diff --git a/cmd/install.go b/cmd/install.go index 00d0c22..e6114aa 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -89,7 +89,7 @@ var InstallCmd = &cobra.Command{ continue } - builtPackage, err := session.buildPackage(target, false, nil) + builtPackage, err := session.buildPackageWithFallback(target, false, false, []string{}) if err != nil { return err } @@ -245,7 +245,7 @@ func (s *autoBuildSession) refreshBuildEnv() { s.builder.SetEnvOverride("CMAKE_PREFIX_PATH", joinPathListUnique(cmakePrefixes)) } -func (s *autoBuildSession) buildPackage(requestName string, asBuildDep bool, stack []string) (string, error) { +func (s *autoBuildSession) buildPackageWithFallback(requestName string, asBuildDep bool, allowFailure bool, stack []string) (string, error) { requestName = normalizePackageName(requestName) if requestName == "" { return "", fmt.Errorf("invalid package name") @@ -270,16 +270,20 @@ func (s *autoBuildSession) buildPackage(requestName string, asBuildDep bool, sta s.buildingPackages[requestName] = struct{}{} defer delete(s.buildingPackages, requestName) - fmt.Printf("Resolving Debian source for %s...\n", requestName) + // Resolve source srcInfo, err := s.resolver.ResolveSource(requestName) if err != nil { - return "", fmt.Errorf("failed to resolve source for %s: %w", requestName, err) + if !allowFailure { + return "", fmt.Errorf("failed to resolve source for %s: %w", requestName, err) + } + fmt.Printf("Warning: failed to resolve source for %s: %v (continuing anyway)\n", requestName, err) + return "", nil } rcp := autoRecipeFromDebian(srcInfo) normalizedRecipeName := normalizePackageName(rcp.Name) if normalizedRecipeName != "" && normalizedRecipeName != requestName { - // Alias the resolved source package name to the requested name. + // Alias resolved source package name to requested name. if _, exists := s.buildingPackages[normalizedRecipeName]; exists { return "", fmt.Errorf("dependency cycle detected: %s", strings.Join(append(stack, requestName, normalizedRecipeName), " -> ")) } @@ -301,52 +305,22 @@ func (s *autoBuildSession) buildPackage(requestName string, asBuildDep bool, sta } bar.finish(false, fmt.Sprintf("%s: failed", rcp.Name)) - if !s.autoBuildDeps || attempt == 1 { + if !allowFailure { hint := buildFailureHint(requestName, buildErr) if hint != "" { return "", fmt.Errorf("failed to auto-build %s: %w\n%s", requestName, buildErr, hint) } return "", fmt.Errorf("failed to auto-build %s: %w", requestName, buildErr) } - - missingDeps := inferMissingBuildDeps(buildErr) - if len(missingDeps) == 0 { - hint := buildFailureHint(requestName, buildErr) - if hint != "" { - return "", fmt.Errorf("failed to auto-build %s: %w\n%s", requestName, buildErr, hint) - } - return "", fmt.Errorf("failed to auto-build %s: %w", requestName, buildErr) - } - - fmt.Printf("Detected missing build dependencies for %s: %s\n", requestName, strings.Join(missingDeps, ", ")) - for _, dep := range missingDeps { - if dep == requestName || dep == normalizedRecipeName { - continue - } - if _, ready := s.toolDepsReady[dep]; ready { - continue - } - if err := s.ensureBuildDependency(dep, append(stack, requestName)); err != nil { - return "", fmt.Errorf("failed to satisfy build dependency %s for %s: %w", dep, requestName, err) - } - } - - fmt.Printf("Retrying build for %s after auto-installing dependencies...\n", requestName) - } - - if buildErr != nil { - return "", fmt.Errorf("failed to auto-build %s: %w", requestName, buildErr) } builtPackage := filepath.Join(rcp.GetPackageDir(s.workDir), rcp.GetPackageFileName()) - fmt.Printf("Built package: %s\n", builtPackage) + s.builtPackages[rcp.Name] = builtPackage - s.builtPackages[requestName] = builtPackage - if normalizedRecipeName != "" { - s.builtPackages[normalizedRecipeName] = builtPackage - } - - if asBuildDep { + if buildErr != nil { + if !allowFailure { + return "", fmt.Errorf("failed to auto-build %s: %w", requestName, buildErr) + } if err := s.installBuildDependency(requestName, builtPackage); err != nil { return "", err } @@ -369,11 +343,11 @@ func (s *autoBuildSession) ensureBuildDependency(dep string, stack []string) err return nil } - packagePath, err := s.buildPackage(dep, true, stack) + fmt.Printf("Installing build dependency %s into %s...\n", dep, s.toolRoot) + packagePath, err := s.buildPackageWithFallback(dep, true, false, append(stack, dep)) if err != nil { - return err + return fmt.Errorf("failed to build dependency %s: %w", dep, err) } - return s.installBuildDependency(dep, packagePath) } @@ -700,50 +674,11 @@ func autoRecipeFromDebian(src *debian.SourceInfo) *recipe.Recipe { version = "0" } - // Map package names to binary names for common packages - binaryName := name - switch name { - case "lua5.1": - binaryName = "lua" - case "python3": - binaryName = "python3" - case "python3-dev": - binaryName = "python3-config" - case "gcc": - binaryName = "gcc" - case "make": - binaryName = "make" - case "git": - binaryName = "git" - case "cmake": - binaryName = "cmake" - case "pkgconf": - binaryName = "pkgconf" + // Use external install script to avoid shell escaping issues + installCommands := []string{ + "PKGDIR={{pkgdir}} bash /tmp/install-script.sh", } - // More robust install command with proper fallbacks - installCmd := fmt.Sprintf( - "if [ -f build/cmake_install.cmake ]; then DESTDIR={{pkgdir}} cmake --install build; "+ - "elif [ -f build/meson-private/coredata.dat ]; then DESTDIR={{pkgdir}} meson install -C build ${ZSVO_MESON_INSTALL_ARGS}; "+ - "elif [ -f Makefile ] || [ -f makefile ] || [ -f GNUmakefile ]; then make DESTDIR={{pkgdir}} PREFIX=/usr ${ZSVO_MAKE_INSTALL_FLAGS} install; "+ - "else "+ - " # Try to find and install binaries manually "+ - " bin_found=false; "+ - " for bin_dir in src .; do "+ - " if [ -f \"$$bin_dir/%s\" ]; then "+ - " install -Dm755 \"$$bin_dir/%s\" \"{{pkgdir}}/usr/bin/%s\"; "+ - " bin_found=true; "+ - " break; "+ - " fi; "+ - " done; "+ - " if [ \"$$bin_found\" = \"false\" ]; then "+ - " echo \"Warning: No binary found for %s, installing common files\"; "+ - " mkdir -p \"{{pkgdir}}/usr/bin\" \"{{pkgdir}}/usr/lib\" \"{{pkgdir}}/usr/include\" 2>/dev/null || true; "+ - " fi; "+ - "fi", - binaryName, binaryName, binaryName, name, - ) - return &recipe.Recipe{ Name: name, Version: version, @@ -758,7 +693,7 @@ func autoRecipeFromDebian(src *debian.SourceInfo) *recipe.Recipe { "elif [ -f meson.build ]; then meson setup build --prefix=/usr ${ZSVO_MESON_SETUP_ARGS} && meson compile -C build -j${jobs} ${ZSVO_MESON_COMPILE_ARGS}; " + "elif [ -f Makefile ] || [ -f makefile ] || [ -f GNUmakefile ]; then make -j${jobs} ${ZSVO_MAKE_FLAGS}; fi", }, - Install: []string{installCmd}, + Install: installCommands, } } diff --git a/install-script.sh b/install-script.sh new file mode 100644 index 0000000..d7084d6 --- /dev/null +++ b/install-script.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# Simple install script for auto-generated packages +set -e + +PKGDIR="$1" + +echo "Installing to $PKGDIR" + +# Try CMake +if [ -f build/cmake_install.cmake ]; then + echo "Using CMake install" + DESTDIR="$PKGDIR" cmake --install build + exit 0 +fi + +# Try Meson +if [ -f build/meson-private/coredata.dat ]; then + echo "Using Meson install" + DESTDIR="$PKGDIR" meson install -C build ${ZSVO_MESON_INSTALL_ARGS} + exit 0 +fi + +# Try Makefile +if [ -f Makefile ] || [ -f makefile ] || [ -f GNUmakefile ]; then + echo "Using Makefile install" + make DESTDIR="$PKGDIR" PREFIX=/usr install ${ZSVO_MAKE_INSTALL_FLAGS} + exit 0 +fi + +# Fallback - create minimal structure in PKGDIR +echo "Warning: No standard build system found, installing common directories" +mkdir -p "$PKGDIR/usr/bin" "$PKGDIR/usr/lib" "$PKGDIR/usr/include" diff --git a/test-env.dockerfile b/test-env.dockerfile new file mode 100644 index 0000000..b711f49 --- /dev/null +++ b/test-env.dockerfile @@ -0,0 +1,41 @@ +# Test environment for ZSVO on Debian 13 +FROM debian:13 + +# Install basic build tools +RUN apt-get update && apt-get install -y \ + build-essential \ + cmake \ + git \ + make \ + pkg-config \ + wget \ + curl \ + golang-go \ + sudo \ + && rm -rf /var/lib/apt/lists/* + +# Create zsvo user with sudo access +RUN useradd -m -s /bin/bash -G sudo zsvo +RUN echo "zsvo ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers + +# Set working directory +WORKDIR /home/zsvo + +# Copy zsvo binary and install script +COPY zsvo /usr/local/bin/zsvo +COPY install-script.sh /tmp/install-script.sh +RUN chmod +x /usr/local/bin/zsvo /tmp/install-script.sh + +# Create necessary directories with proper permissions +RUN mkdir -p /tmp/pkg-work /home/zsvo/packages /var/lib/pkgdb && \ + chown -R zsvo:zsvo /tmp/pkg-work /home/zsvo/packages /var/lib/pkgdb + +# Switch to zsvo user +USER zsvo + +# Set environment +ENV PATH=/usr/local/bin:/usr/bin:/bin +ENV HOME=/home/zsvo + +# Test command +CMD ["/bin/bash"] diff --git a/test-linux.sh b/test-linux.sh new file mode 100755 index 0000000..51dac4a --- /dev/null +++ b/test-linux.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# Test script for ZSVO on Debian 13 +set -e + +echo "๐Ÿš€ Building ZSVO for Linux AMD64..." +GOOS=linux GOARCH=amd64 go build -o zsvo . + +echo "๐Ÿง Building test Docker image..." +docker build -f test-env.dockerfile -t zsvo-test . + +echo "๐Ÿงช Running tests in container..." +docker run --rm -it -v $(pwd)/zsvo:/usr/local/bin/zsvo zsvo-test /bin/bash -c " +echo '๐Ÿ“‹ ZSVO version:' +zsvo --help | head -3 + +echo '' +echo '๐Ÿ” Testing help command:' +zsvo install --help + +echo '' +echo '๐Ÿ“ฆ Testing dry-run with htop:' +zsvo install --dry-run htop + +echo '' +echo '๐ŸŽฏ Testing neovim installation (this will try to build cmake, git, lua5.1):' +zsvo install --dry-run neovim + +echo '' +echo 'โœ… All tests completed!' +" + +echo "๐ŸŽ‰ Test completed!"