diff --git a/cli/module_build.go b/cli/module_build.go index 8b454c256a3..9d52749cbfd 100644 --- a/cli/module_build.go +++ b/cli/module_build.go @@ -1324,7 +1324,7 @@ func reloadModuleActionInner( "try --local if you are testing on the same machine.", ) } - if err := validateReloadableArchive(cmd, manifest.Build); err != nil { + if err := validateReloadableArchive(cmd, manifest.Build, manifest.FirstRun); err != nil { return err } @@ -1475,8 +1475,9 @@ func reloadingDestination(cmd *cli.Command, manifest *ModuleManifest) string { } // validateReloadableArchive returns an error if there is a fatal issue (for now just file not found). -// It also logs warnings for likely problems. -func validateReloadableArchive(cmd *cli.Command, build *manifestBuildInfo) error { +// It also logs warnings for likely problems, such as a missing meta.json or a first_run script +// declared in the manifest but absent from the archive. +func validateReloadableArchive(cmd *cli.Command, build *manifestBuildInfo, firstRun string) error { reader, err := os.Open(build.Path) if err != nil { return errors.Wrap(err, "error opening the build.path field in your meta.json") @@ -1487,6 +1488,7 @@ func validateReloadableArchive(cmd *cli.Command, build *manifestBuildInfo) error } archive := tar.NewReader(decompressed) metaFound := false + firstRunFound := false for { header, err := archive.Next() if errors.Is(err, io.EOF) { @@ -1495,14 +1497,26 @@ func validateReloadableArchive(cmd *cli.Command, build *manifestBuildInfo) error if err != nil { return errors.Wrapf(err, "reading tar at %s", build.Path) } - if header.Name == "meta.json" { + name := filepath.Base(header.Name) + if name == "meta.json" { metaFound = true + } + if firstRun != "" && name == filepath.Base(firstRun) { + firstRunFound = true + } + if metaFound && (firstRun == "" || firstRunFound) { break } } if !metaFound { warningf(cmd.Root().ErrWriter, "archive at %s doesn't contain a meta.json, your module will probably fail to start", build.Path) } + if firstRun != "" && !firstRunFound { + warningf(cmd.Root().ErrWriter, + "archive at %s doesn't contain the first_run script %q declared in meta.json; "+ + "the script will not run on the target machine. Include it in your build artifact", + build.Path, firstRun) + } return nil } diff --git a/cli/module_generate/_templates/go/tmpl-Makefile b/cli/module_generate/_templates/go/tmpl-Makefile index 1399364cbc3..cd069d8df33 100644 --- a/cli/module_generate/_templates/go/tmpl-Makefile +++ b/cli/module_generate/_templates/go/tmpl-Makefile @@ -22,11 +22,17 @@ update: test: go test ./... +FIRST_RUN := $(shell jq -r '.first_run // empty' meta.json 2>/dev/null) +TAR_FILES := meta.json $(MODULE_BINARY) +ifneq ($(FIRST_RUN),) +TAR_FILES += $(FIRST_RUN) +endif + module.tar.gz: meta.json $(MODULE_BINARY) ifneq ($(VIAM_TARGET_OS), windows) strip $(MODULE_BINARY) endif - tar czf $@ meta.json $(MODULE_BINARY) + tar czf $@ $(TAR_FILES) module: test module.tar.gz diff --git a/cli/module_generate/_templates/python/build.sh b/cli/module_generate/_templates/python/build.sh index 3e6a933c3d4..0f2b953f021 100644 --- a/cli/module_generate/_templates/python/build.sh +++ b/cli/module_generate/_templates/python/build.sh @@ -10,4 +10,10 @@ if ! $PYTHON -m pip install pyinstaller -Uqq; then fi $PYTHON -m PyInstaller --onefile --hidden-import="googleapiclient" src/main.py -tar -czvf dist/archive.tar.gz meta.json ./dist/main + +TAR_FILES="meta.json ./dist/main" +FIRST_RUN=$($PYTHON -c "import json; print(json.load(open('meta.json')).get('first_run', ''))" 2>/dev/null) +if [ -n "$FIRST_RUN" ] && [ -f "$FIRST_RUN" ]; then + TAR_FILES="$TAR_FILES $FIRST_RUN" +fi +tar -czvf dist/archive.tar.gz $TAR_FILES diff --git a/cli/module_generate/cpp-gen/src/viam/generator/generator.cpp b/cli/module_generate/cpp-gen/src/viam/generator/generator.cpp index 3e28745efd7..db298f0bc58 100644 --- a/cli/module_generate/cpp-gen/src/viam/generator/generator.cpp +++ b/cli/module_generate/cpp-gen/src/viam/generator/generator.cpp @@ -379,6 +379,12 @@ install( DESTINATION . ) +file(READ "${{CMAKE_CURRENT_SOURCE_DIR}}/meta.json" _META_JSON) +string(JSON _FIRST_RUN ERROR_VARIABLE _FR_ERR GET "${{_META_JSON}}" "first_run") +if(NOT _FR_ERR AND _FIRST_RUN AND EXISTS "${{CMAKE_CURRENT_SOURCE_DIR}}/${{_FIRST_RUN}}") + install(FILES "${{_FIRST_RUN}}" DESTINATION .) +endif() + install(TARGETS {0}) set(CPACK_PACKAGE_NAME "{0}") @@ -415,6 +421,16 @@ class {0}Recipe(ConanFile): # Sources are located in the same place as this recipe, copy them to the recipe exports_sources = "CMakeLists.txt", "src/*", "main.cpp", "meta.json" + def export_sources(self): + import json, os + from conan.tools.files import copy + meta_path = os.path.join(self.recipe_folder, "meta.json") + if os.path.exists(meta_path): + with open(meta_path) as f: + first_run = json.load(f).get("first_run", "") + if first_run and os.path.exists(os.path.join(self.recipe_folder, first_run)): + copy(self, first_run, src=self.recipe_folder, dst=self.export_sources_folder) + def layout(self): cmake_layout(self)