diff --git a/cmd/codegen/generator/generator.go b/cmd/codegen/generator/generator.go
index 8b8e7367834..83b581cc2ba 100644
--- a/cmd/codegen/generator/generator.go
+++ b/cmd/codegen/generator/generator.go
@@ -58,6 +58,10 @@ type Config struct {
// name is the expected value.
IsInit bool
+ // TypeDefsOnly indicates whether only type definitions should be generated, excluding other related code artifacts.
+ // This is used to generate module own's types even if the module doesn't compile.
+ TypeDefsOnly bool
+
// ClientOnly indicates that the codegen should only generate the client code.
ClientOnly bool
diff --git a/cmd/codegen/generator/go/templates/modules.go b/cmd/codegen/generator/go/templates/modules.go
index f144179d787..8eb7ee0b1bb 100644
--- a/cmd/codegen/generator/go/templates/modules.go
+++ b/cmd/codegen/generator/go/templates/modules.go
@@ -284,11 +284,18 @@ func (funcs goTemplateFuncs) moduleMainSrc() (string, error) { //nolint: gocyclo
tps, nextTps = nextTps, nil
}
- return strings.Join([]string{
- fmt.Sprintf("%#v", implementationCode),
- mainSrc(funcs.CheckVersionCompatibility),
- invokeSrc(objFunctionCases, createMod),
- }, "\n"), nil
+ var out []string
+ if !funcs.cfg.TypeDefsOnly {
+ out = append(out, fmt.Sprintf("%#v", implementationCode))
+ }
+ out = append(out,
+ mainSrc(funcs.CheckVersionCompatibility, funcs.cfg.TypeDefsOnly),
+ registerSrc(createMod),
+ )
+ if !funcs.cfg.TypeDefsOnly {
+ out = append(out, invokeSrc(objFunctionCases))
+ }
+ return strings.Join(out, "\n"), nil
}
func dotLine(a *Statement, id string) *Statement {
@@ -296,61 +303,68 @@ func dotLine(a *Statement, id string) *Statement {
}
const (
- parentJSONVar = "parentJSON"
- parentNameVar = "parentName"
- fnNameVar = "fnName"
- inputArgsVar = "inputArgs"
- invokeFuncName = "invoke"
+ parentJSONVar = "parentJSON"
+ parentNameVar = "parentName"
+ fnNameVar = "fnName"
+ inputArgsVar = "inputArgs"
+ invokeFuncName = "invoke"
+ registerFuncName = "register"
)
// mainSrc returns the static part of the generated code. It calls out to the
// "invoke" func, which is the mostly dynamically generated code that actually
// calls the user's functions.
-func mainSrc(checkVersionCompatibility func(string) bool) string {
+func mainSrc(checkVersionCompatibility func(string) bool, typeDefsOnly bool) string {
// Ensure compatibility with modules that predate Void return value handling
voidRet := `err`
if !checkVersionCompatibility("v0.12.0") {
voidRet = `_, err`
}
- return `func main() {
- ctx := context.Background()
-
- // Direct slog to the new stderr. This is only for dev time debugging, and
- // runtime errors/warnings.
- slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
- Level: slog.LevelWarn,
- })))
+ var dispatch string
+ if typeDefsOnly {
+ dispatch = `func dispatch(ctx context.Context) (rerr error) {
+ ctx = telemetry.InitEmbedded(ctx, resource.NewWithAttributes(
+ semconv.SchemaURL,
+ semconv.ServiceNameKey.String("dagger-go-sdk"),
+ // TODO version?
+ ))
+ defer telemetry.Close()
- if err := dispatch(ctx); err != nil {
- os.Exit(2)
- }
-}
+ // A lot of the "work" actually happens when we're marshalling the return
+ // value, which entails getting object IDs, which happens in MarshalJSON,
+ // which has no ctx argument, so we use this lovely global variable.
+ setMarshalContext(ctx)
-func convertError(rerr error) *dagger.Error {
- var gqlErr *gqlerror.Error
- if errors.As(rerr, &gqlErr) {
- dagErr := dag.Error(gqlErr.Message)
- if gqlErr.Extensions != nil {
- keys := make([]string, 0, len(gqlErr.Extensions))
- for k := range gqlErr.Extensions {
- keys = append(keys, k)
- }
- sort.Strings(keys)
- for _, k := range keys {
- val, err := json.Marshal(gqlErr.Extensions[k])
- if err != nil {
- fmt.Println("failed to marshal error value:", err)
- }
- dagErr = dagErr.WithValue(k, dagger.JSON(val))
+ fnCall := dag.CurrentFunctionCall()
+ defer func() {
+ if rerr != nil {
+ if ` + voidRet + ` := fnCall.ReturnError(ctx, convertError(rerr)); err != nil {
+ fmt.Println("failed to return error:", err, "\noriginal error:", rerr)
}
}
- return dagErr
+ }()
+
+ result, err := register()
+ if err != nil {
+ var exec *dagger.ExecError
+ if errors.As(err, &exec) {
+ return exec.Unwrap()
+ }
+ return err
+ }
+ resultBytes, err := json.Marshal(result)
+ if err != nil {
+ return fmt.Errorf("marshal: %w", err)
}
- return dag.Error(rerr.Error())
-}
-func dispatch(ctx context.Context) (rerr error) {
+ if ` + voidRet + ` := fnCall.ReturnValue(ctx, dagger.JSON(resultBytes)); err != nil {
+ return fmt.Errorf("store return value: %w", err)
+ }
+ return nil
+}`
+ } else {
+ dispatch = `func dispatch(ctx context.Context) (rerr error) {
ctx = telemetry.InitEmbedded(ctx, resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("dagger-go-sdk"),
@@ -420,10 +434,50 @@ func dispatch(ctx context.Context) (rerr error) {
}
return nil
}`
+ }
+
+ return `func main() {
+ ctx := context.Background()
+
+ // Direct slog to the new stderr. This is only for dev time debugging, and
+ // runtime errors/warnings.
+ slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
+ Level: slog.LevelWarn,
+ })))
+
+ if err := dispatch(ctx); err != nil {
+ os.Exit(2)
+ }
+}
+
+func convertError(rerr error) *dagger.Error {
+ var gqlErr *gqlerror.Error
+ if errors.As(rerr, &gqlErr) {
+ dagErr := dag.Error(gqlErr.Message)
+ if gqlErr.Extensions != nil {
+ keys := make([]string, 0, len(gqlErr.Extensions))
+ for k := range gqlErr.Extensions {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ for _, k := range keys {
+ val, err := json.Marshal(gqlErr.Extensions[k])
+ if err != nil {
+ fmt.Println("failed to marshal error value:", err)
+ }
+ dagErr = dagErr.WithValue(k, dagger.JSON(val))
+ }
+ }
+ return dagErr
+ }
+ return dag.Error(rerr.Error())
+}
+
+` + dispatch
}
// the source code of the invoke func, which is the mostly dynamically generated code that actually calls the user's functions
-func invokeSrc(objFunctionCases map[string][]Code, createMod Code) string {
+func invokeSrc(objFunctionCases map[string][]Code) string {
// each `case` statement for every object name, which makes up the body of the invoke func
objNames := []string{}
for objName := range objFunctionCases {
@@ -437,7 +491,7 @@ func invokeSrc(objFunctionCases map[string][]Code, createMod Code) string {
}
// when the object name is empty, return the module definition
objCases = append(objCases, Case(Lit("")).Block(
- Return(createMod, Nil()),
+ Return(Id(registerFuncName).Call()),
))
// default case (return error)
objCases = append(objCases, Default().Block(
@@ -471,6 +525,21 @@ func invokeSrc(objFunctionCases map[string][]Code, createMod Code) string {
return fmt.Sprintf("%#v", invokeFunc)
}
+// the source code of the register func, which exposes the module's defined types
+func registerSrc(createMod Code) string {
+ // func register(
+ invokeFunc := Func().Id(registerFuncName).Params().Params(
+ // ) (_ any,
+ Id("_").Id("any"),
+ // err error)
+ Id("err").Error(),
+ ).Block(
+ Return(createMod, Nil()),
+ )
+
+ return fmt.Sprintf("%#v", invokeFunc)
+}
+
// TODO: use jennifer for generating this magical typedef
func (ps *parseState) renderNameOrStruct(t types.Type) string {
if alias, ok := t.(*types.Alias); ok {
diff --git a/cmd/codegen/main.go b/cmd/codegen/main.go
index 6b583793bfe..3c6039eda97 100644
--- a/cmd/codegen/main.go
+++ b/cmd/codegen/main.go
@@ -29,7 +29,8 @@ var (
clientOnly bool
- isInit bool
+ isInit bool
+ typeDefsOnly bool
bundle bool
@@ -63,6 +64,7 @@ func init() {
rootCmd.Flags().StringVar(&moduleName, "module-name", "", "name of module to generate code for")
rootCmd.Flags().BoolVar(&merge, "merge", false, "merge module deps with project's existing go.mod in a parent directory")
rootCmd.Flags().BoolVar(&isInit, "is-init", false, "whether this command is initializing a new module")
+ rootCmd.Flags().BoolVar(&typeDefsOnly, "typedefs-only", false, "generate only type definitions (no client code)")
rootCmd.Flags().BoolVar(&clientOnly, "client-only", false, "generate only client code")
rootCmd.Flags().BoolVar(&bundle, "bundle", false, "generate the client in bundle mode")
rootCmd.Flags().StringVar(&moduleSourceID, "module-source-id", "", "id of the module source to generate code for")
@@ -76,12 +78,13 @@ func ClientGen(cmd *cobra.Command, args []string) error {
ctx = telemetry.InitEmbedded(ctx, nil)
cfg := generator.Config{
- Lang: generator.SDKLang(lang),
- OutputDir: outputDir,
- Merge: merge,
- IsInit: isInit,
- ClientOnly: clientOnly,
- Bundle: bundle,
+ Lang: generator.SDKLang(lang),
+ OutputDir: outputDir,
+ Merge: merge,
+ IsInit: isInit,
+ TypeDefsOnly: typeDefsOnly,
+ ClientOnly: clientOnly,
+ Bundle: bundle,
}
// If a module source ID is provided or no introspection JSON is provided, we will query
diff --git a/core/schema/modulesource.go b/core/schema/modulesource.go
index 3a8e6da9a6d..85bfc9909a1 100644
--- a/core/schema/modulesource.go
+++ b/core/schema/modulesource.go
@@ -2010,9 +2010,9 @@ func (s *moduleSourceSchema) runModuleDefInSDK(ctx context.Context, src, srcInst
return nil, ErrSDKRuntimeNotImplemented{SDK: src.Self.SDK.Source}
}
- // get the runtime container, which is what is exec'd when calling functions in the module
- var err error
- mod.Runtime, err = runtimeImpl.Runtime(ctx, mod.Deps, srcInstContentHashed)
+ // get the typedefs container dedicated to get the module's definition.
+ // this will fall back to the runtime container if `moduleTypeDefs` is not defined.
+ typeDefs, err := runtimeImpl.TypeDefs(ctx, mod.Deps, srcInstContentHashed)
if err != nil {
return nil, fmt.Errorf("failed to get module runtime: %w", err)
}
@@ -2048,7 +2048,7 @@ func (s *moduleSourceSchema) runModuleDefInSDK(ctx context.Context, src, srcInst
ctx,
mod,
nil,
- mod.Runtime,
+ typeDefs,
core.NewFunction("", &core.TypeDef{
Kind: core.TypeDefKindObject,
AsObject: dagql.NonNull(core.NewObjectTypeDef("Module", "")),
@@ -2162,10 +2162,24 @@ func (s *moduleSourceSchema) moduleSourceAsModule(
modName := src.Self.ModuleName
if src.Self.SDKImpl != nil {
+ runtimeImpl, ok := src.Self.SDKImpl.AsRuntime()
+ if !ok {
+ return inst, ErrSDKRuntimeNotImplemented{SDK: src.Self.SDK.Source}
+ }
+
mod, err = s.runModuleDefInSDK(ctx, src, srcInstContentHashed, mod)
if err != nil {
return inst, err
}
+
+ // pre-load the module Runtime
+ if mod.Runtime == nil {
+ mod.Runtime, err = runtimeImpl.Runtime(ctx, mod.Deps, srcInstContentHashed)
+ if err != nil {
+ return inst, err
+ }
+ }
+
mod.InstanceID = dagql.CurrentID(ctx)
} else {
// For no SDK, provide an empty stub module definition
diff --git a/core/sdk.go b/core/sdk.go
index d4f77d7065a..164eb988376 100644
--- a/core/sdk.go
+++ b/core/sdk.go
@@ -168,6 +168,40 @@ type Runtime interface {
// Current instance of the module source.
dagql.Instance[*ModuleSource],
) (*Container, error)
+
+ /*
+ HasModuleTypeDefs checks if the module exposes a `moduleTypeDefs` function
+ to be called by `TypeDefs`.
+
+ This doesn't rely on a function exposed by the SDK, but on the list of functions
+ exposed.
+ */
+ HasModuleTypeDefs() bool
+
+ /*
+ TypeDefs returns a container that is used to execute module code
+ to retrieve the types defined by the module.
+
+ This function will call the following exposed by the SDK:
+
+ ```gql
+ moduleTypeDefs(
+ modSource: ModuleSource!
+ introspectionJSON: File!
+ ): Container!
+ ```
+
+ If this function is not exposed, it will fallback to `Runtime`.
+ */
+ TypeDefs(
+ context.Context,
+
+ // Current module dependencies.
+ *ModDeps,
+
+ // Current instance of the module source.
+ dagql.Instance[*ModuleSource],
+ ) (*Container, error)
}
/*
diff --git a/core/sdk/consts.go b/core/sdk/consts.go
index e6266b43598..33817e21c8b 100644
--- a/core/sdk/consts.go
+++ b/core/sdk/consts.go
@@ -31,6 +31,7 @@ var sdkFunctions = []string{
"withConfig",
"codegen",
"moduleRuntime",
+ "moduleTypeDefs",
"requiredClientGenerationFiles",
"generateClient",
}
diff --git a/core/sdk/go_sdk.go b/core/sdk/go_sdk.go
index d21dc99e338..3d6fc839030 100644
--- a/core/sdk/go_sdk.go
+++ b/core/sdk/go_sdk.go
@@ -37,6 +37,10 @@ type goSDK struct {
rawConfig map[string]any
}
+func (sdk *goSDK) HasModuleTypeDefs() bool {
+ return true
+}
+
type goSDKConfig struct {
GoPrivate string `json:"goprivate,omitempty"`
}
@@ -170,7 +174,7 @@ func (sdk *goSDK) Codegen(
) (_ *core.GeneratedCode, rerr error) {
ctx, span := core.Tracer(ctx).Start(ctx, "go SDK: run codegen")
defer telemetry.End(span, func() error { return rerr })
- ctr, err := sdk.baseWithCodegen(ctx, deps, source)
+ ctr, err := sdk.baseWithCodegen(ctx, deps, source, false)
if err != nil {
return nil, err
}
@@ -206,12 +210,111 @@ func (sdk *goSDK) Codegen(
}, nil
}
+func (sdk *goSDK) TypeDefs(
+ ctx context.Context,
+ deps *core.ModDeps,
+ source dagql.Instance[*core.ModuleSource],
+) (_ *core.Container, rerr error) {
+ ctx, span := core.Tracer(ctx).Start(ctx, "go SDK: load typedefs")
+ defer telemetry.End(span, func() error { return rerr })
+ ctr, err := sdk.baseWithCodegen(ctx, deps, source, true)
+ if err != nil {
+ return nil, err
+ }
+ if err := sdk.dag.Select(ctx, ctr, &ctr,
+ dagql.Selector{
+ Field: "withoutUnixSocket",
+ Args: []dagql.NamedInput{
+ {
+ Name: "path",
+ Value: dagql.NewString("/tmp/dagger-ssh-sock"),
+ },
+ },
+ },
+ dagql.Selector{
+ Field: "withExec",
+ Args: []dagql.NamedInput{
+ {
+ Name: "args",
+ Value: dagql.ArrayInput[dagql.String]{
+ "find", ".",
+ "-maxdepth", "1",
+ "-type", "f",
+ "-name", "*.go",
+ "!", "-name", "dagger.gen.go",
+ "-delete",
+ },
+ },
+ },
+ },
+ dagql.Selector{
+ Field: "withExec",
+ Args: []dagql.NamedInput{
+ {
+ Name: "args",
+ Value: dagql.ArrayInput[dagql.String]{
+ "go", "build",
+ "-ldflags", "-s -w", // strip DWARF debug symbols to save a few MBs of space
+ "-o", goSDKRuntimePath,
+ ".",
+ },
+ },
+ },
+ },
+ dagql.Selector{
+ Field: "withEntrypoint",
+ Args: []dagql.NamedInput{
+ {
+ Name: "args",
+ Value: dagql.ArrayInput[dagql.String]{
+ goSDKRuntimePath,
+ },
+ },
+ },
+ },
+ dagql.Selector{
+ Field: "withWorkdir",
+ Args: []dagql.NamedInput{
+ {
+ Name: "path",
+ Value: dagql.NewString(RuntimeWorkdirPath),
+ },
+ },
+ },
+ // remove shared cache mounts from final container so module code can't
+ // do weird things with them like IPC, etc.
+ dagql.Selector{
+ Field: "withoutMount",
+ Args: []dagql.NamedInput{
+ {
+ Name: "path",
+ Value: dagql.String("/go/pkg/mod"),
+ },
+ },
+ },
+ dagql.Selector{
+ Field: "withoutMount",
+ Args: []dagql.NamedInput{
+ {
+ Name: "path",
+ Value: dagql.String("/root/.cache/go-build"),
+ },
+ },
+ },
+ ); err != nil {
+ return nil, fmt.Errorf("failed to build go runtime binary: %w", err)
+ }
+ return ctr.Self, nil
+}
+
func (sdk *goSDK) Runtime(
ctx context.Context,
deps *core.ModDeps,
source dagql.Instance[*core.ModuleSource],
) (_ *core.Container, rerr error) {
- ctr, err := sdk.baseWithCodegen(ctx, deps, source)
+ ctx, span := core.Tracer(ctx).Start(ctx, "go SDK: load runtime")
+ defer telemetry.End(span, func() error { return rerr })
+ ctr, err := sdk.baseWithCodegen(ctx, deps, source, false)
if err != nil {
return nil, err
}
@@ -289,6 +392,7 @@ func (sdk *goSDK) baseWithCodegen(
ctx context.Context,
deps *core.ModDeps,
src dagql.Instance[*core.ModuleSource],
+ typedefsOnly bool,
) (dagql.Instance[*core.Container], error) {
var ctr dagql.Instance[*core.Container]
@@ -334,6 +438,10 @@ func (sdk *goSDK) baseWithCodegen(
codegenArgs = append(codegenArgs, "--is-init")
}
+ if typedefsOnly {
+ codegenArgs = append(codegenArgs, "--typedefs-only")
+ }
+
selectors := []dagql.Selector{
{
Field: "withMountedFile",
diff --git a/core/sdk/module_runtime.go b/core/sdk/module_runtime.go
index f41888189a9..b88c7be8842 100644
--- a/core/sdk/module_runtime.go
+++ b/core/sdk/module_runtime.go
@@ -14,6 +14,31 @@ type runtimeModule struct {
mod *module
}
+func (sdk *runtimeModule) HasModuleTypeDefs() bool {
+ _, ok := sdk.mod.funcs["moduleTypeDefs"]
+ return ok
+}
+
+func (sdk *runtimeModule) TypeDefs(
+ ctx context.Context,
+ deps *core.ModDeps,
+ source dagql.Instance[*core.ModuleSource],
+) (_ *core.Container, rerr error) {
+ if !sdk.HasModuleTypeDefs() {
+ return sdk.Runtime(ctx, deps, source)
+ }
+
+ ctx, span := core.Tracer(ctx).Start(ctx, "module SDK: load typedefs")
+ defer telemetry.End(span, func() error { return rerr })
+
+ schemaJSONFile, err := deps.SchemaIntrospectionJSONFile(ctx, []string{"Host"})
+ if err != nil {
+ return nil, fmt.Errorf("failed to get schema introspection json during %s module sdk runtime: %w", sdk.mod.mod.Self.Name(), err)
+ }
+
+ return sdk.callModuleFn(ctx, "moduleTypeDefs", source, schemaJSONFile)
+}
+
func (sdk *runtimeModule) Runtime(
ctx context.Context,
deps *core.ModDeps,
@@ -26,10 +51,19 @@ func (sdk *runtimeModule) Runtime(
return nil, fmt.Errorf("failed to get schema introspection json during %s module sdk runtime: %w", sdk.mod.mod.Self.Name(), err)
}
+ return sdk.callModuleFn(ctx, "moduleRuntime", source, schemaJSONFile)
+}
+
+func (sdk *runtimeModule) callModuleFn(
+ ctx context.Context,
+ fnName string,
+ source dagql.Instance[*core.ModuleSource],
+ schemaJSONFile dagql.Instance[*core.File],
+) (_ *core.Container, rerr error) {
var inst dagql.Instance[*core.Container]
- err = sdk.mod.dag.Select(ctx, sdk.mod.sdk, &inst,
+ err := sdk.mod.dag.Select(ctx, sdk.mod.sdk, &inst,
dagql.Selector{
- Field: "moduleRuntime",
+ Field: fnName,
Args: []dagql.NamedInput{
{
Name: "modSource",
diff --git a/dagql/idtui/testdata/TestTelemetry/TestGolden/broken-dep/use-broken b/dagql/idtui/testdata/TestTelemetry/TestGolden/broken-dep/use-broken
index ce28d9dfd26..a0afbd0c96c 100644
--- a/dagql/idtui/testdata/TestTelemetry/TestGolden/broken-dep/use-broken
+++ b/dagql/idtui/testdata/TestTelemetry/TestGolden/broken-dep/use-broken
@@ -5,24 +5,19 @@ Expected stderr:
├─● connecting to engine X.Xs
╰─● starting session X.Xs
-▼ load module: ./viztest/broken-dep X.Xs ERROR
+▼ load module: ./viztest/broken-dep X.Xs
├─● finding module configuration X.Xs
-╰─▼ initializing module X.Xs ERROR
- ╰─▼ ModuleSource.asModule: Module! X.Xs ERROR
- ╰─▼ load dep modules X.Xs ERROR
- ╰─▼ ModuleSource.asModule: Module! X.Xs ERROR
- ├─▼ Container.withExec(args: ["go", "build", "-ldflags", "-s -w", "-o", "/runtime", "."]): Container! X.Xs ERROR
- │ ┃ # dagger/broken
- │ ┃ ./main.go:6:6: undefined: ctx
- │ ! process "go build -ldflags -s -w -o /runtime ." did not complete successfully: exit code: 1
- │
- ╰─✘ asModule getModDef X.Xs ERROR
-
-Error logs:
+├─● initializing module X.Xs
+├─● inspecting module metadata X.Xs
+╰─● loading type definitions X.Xs
+
+● parsing command line arguments X.Xs
-▼ Container.withExec(args: ["go", "build", "-ldflags", "-s -w", "-o", "/runtime", "."]): Container! X.Xs ERROR
-# dagger/broken
-./main.go:6:6: undefined: ctx
-! process "go build -ldflags -s -w -o /runtime ." did not complete successfully: exit code: 1
+● brokenDep: BrokenDep! X.Xs
+▼ .useBroken: Void X.Xs ERROR
+! call function "Broken": process "go build -ldflags -s -w -o /runtime ." did not complete successfully: exit code: 1
+├─● broken: Broken! X.Xs
+╰─✘ .broken: Void X.Xs ERROR
+ ! call function "Broken": process "go build -ldflags -s -w -o /runtime ." did not complete successfully: exit code: 1
Setup tracing at https://dagger.cloud/traces/setup. To hide set DAGGER_NO_NAG=1
diff --git a/dagql/idtui/testdata/TestTelemetry/TestGolden/broken/broken b/dagql/idtui/testdata/TestTelemetry/TestGolden/broken/broken
index b3ff8751a5c..38b3d767deb 100644
--- a/dagql/idtui/testdata/TestTelemetry/TestGolden/broken/broken
+++ b/dagql/idtui/testdata/TestTelemetry/TestGolden/broken/broken
@@ -5,22 +5,16 @@ Expected stderr:
├─● connecting to engine X.Xs
╰─● starting session X.Xs
-▼ load module: ./viztest/broken-dep/broken X.Xs ERROR
+▼ load module: ./viztest/broken-dep/broken X.Xs
├─● finding module configuration X.Xs
-╰─▼ initializing module X.Xs ERROR
- ╰─▼ ModuleSource.asModule: Module! X.Xs ERROR
- ├─▼ Container.withExec(args: ["go", "build", "-ldflags", "-s -w", "-o", "/runtime", "."]): Container! X.Xs ERROR
- │ ┃ # dagger/broken
- │ ┃ ./main.go:6:6: undefined: ctx
- │ ! process "go build -ldflags -s -w -o /runtime ." did not complete successfully: exit code: 1
- │
- ╰─✘ asModule getModDef X.Xs ERROR
-
-Error logs:
+├─● initializing module X.Xs
+├─● inspecting module metadata X.Xs
+╰─● loading type definitions X.Xs
+
+● parsing command line arguments X.Xs
-▼ Container.withExec(args: ["go", "build", "-ldflags", "-s -w", "-o", "/runtime", "."]): Container! X.Xs ERROR
-# dagger/broken
-./main.go:6:6: undefined: ctx
-! process "go build -ldflags -s -w -o /runtime ." did not complete successfully: exit code: 1
+● broken: Broken! X.Xs
+✘ .broken: Void X.Xs ERROR
+! call function "Broken": process "go build -ldflags -s -w -o /runtime ." did not complete successfully: exit code: 1
Setup tracing at https://dagger.cloud/traces/setup. To hide set DAGGER_NO_NAG=1
diff --git a/sdk/java/dagger-java-annotation-processor/pom.xml b/sdk/java/dagger-java-annotation-processor/pom.xml
index d70cafb1197..8c47708b3a2 100644
--- a/sdk/java/dagger-java-annotation-processor/pom.xml
+++ b/sdk/java/dagger-java-annotation-processor/pom.xml
@@ -76,6 +76,22 @@
--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.5.0
+
+
+ package
+ shade
+
+ false
+ true
+ all
+
+
+
+
diff --git a/sdk/java/dagger-java-annotation-processor/src/main/java/io/dagger/annotation/processor/DaggerModuleAnnotationProcessor.java b/sdk/java/dagger-java-annotation-processor/src/main/java/io/dagger/annotation/processor/DaggerModuleAnnotationProcessor.java
index f5f72b8529c..fce215a2755 100644
--- a/sdk/java/dagger-java-annotation-processor/src/main/java/io/dagger/annotation/processor/DaggerModuleAnnotationProcessor.java
+++ b/sdk/java/dagger-java-annotation-processor/src/main/java/io/dagger/annotation/processor/DaggerModuleAnnotationProcessor.java
@@ -1,9 +1,5 @@
package io.dagger.annotation.processor;
-import com.github.javaparser.StaticJavaParser;
-import com.github.javaparser.javadoc.Javadoc;
-import com.github.javaparser.javadoc.JavadocBlockTag;
-import com.github.javaparser.javadoc.JavadocBlockTag.Type;
import com.google.auto.service.AutoService;
import com.palantir.javapoet.ClassName;
import com.palantir.javapoet.CodeBlock;
@@ -16,37 +12,19 @@
import io.dagger.client.FunctionCallArgValue;
import io.dagger.client.JSON;
import io.dagger.client.JsonConverter;
-import io.dagger.client.ModuleID;
-import io.dagger.client.TypeDef;
import io.dagger.client.exception.DaggerExecException;
-import io.dagger.client.exception.DaggerQueryException;
-import io.dagger.module.annotation.Default;
-import io.dagger.module.annotation.DefaultPath;
-import io.dagger.module.annotation.Enum;
-import io.dagger.module.annotation.Function;
-import io.dagger.module.annotation.Ignore;
-import io.dagger.module.annotation.Module;
-import io.dagger.module.annotation.Object;
-import io.dagger.module.info.EnumInfo;
-import io.dagger.module.info.EnumValueInfo;
-import io.dagger.module.info.FieldInfo;
import io.dagger.module.info.FunctionInfo;
import io.dagger.module.info.ModuleInfo;
import io.dagger.module.info.ObjectInfo;
-import io.dagger.module.info.ParameterInfo;
-import io.dagger.module.info.TypeInfo;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
-import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
@@ -55,14 +33,9 @@
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.ElementKind;
-import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
-import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
-import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
@SupportedAnnotationTypes({
@@ -86,499 +59,153 @@ public synchronized void init(ProcessingEnvironment processingEnv) {
this.elementUtils = processingEnv.getElementUtils(); // Récupération d'Elements
}
- ModuleInfo generateModuleInfo(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
- String moduleName = System.getenv("_DAGGER_JAVA_SDK_MODULE_NAME");
-
- String moduleDescription = null;
- Set annotatedObjects = new HashSet<>();
- boolean hasModuleAnnotation = false;
-
- Map enumInfos = new HashMap<>();
-
- for (TypeElement annotation : annotations) {
- for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
- switch (element.getKind()) {
- case ENUM -> {
- if (element.getAnnotation(Enum.class) != null) {
- String qName = ((TypeElement) element).getQualifiedName().toString();
- if (!enumInfos.containsKey(qName)) {
- enumInfos.put(
- qName,
- new EnumInfo(
- element.getSimpleName().toString(),
- parseJavaDocDescription(element),
- element.getEnclosedElements().stream()
- .filter(elt -> elt.getKind() == ElementKind.ENUM_CONSTANT)
- .map(
- elt ->
- new EnumValueInfo(
- elt.getSimpleName().toString(),
- parseJavaDocDescription(elt)))
- .toArray(EnumValueInfo[]::new)));
- }
- }
- }
- case PACKAGE -> {
- if (hasModuleAnnotation) {
- throw new IllegalStateException("Only one @Module annotation is allowed");
- }
- hasModuleAnnotation = true;
- moduleDescription = parseModuleDescription(element);
- }
- case CLASS, RECORD -> {
- TypeElement typeElement = (TypeElement) element;
- String qName = typeElement.getQualifiedName().toString();
- String name = typeElement.getAnnotation(Object.class).value();
- if (name.isEmpty()) {
- name = typeElement.getSimpleName().toString();
- }
-
- boolean mainObject = areSimilar(name, moduleName);
-
- if (!element.getModifiers().contains(Modifier.PUBLIC)) {
- throw new RuntimeException(
- "The class %s must be public if annotated with @Object".formatted(qName));
- }
-
- boolean hasDefaultConstructor =
- typeElement.getEnclosedElements().stream()
- .filter(elt -> elt.getKind() == ElementKind.CONSTRUCTOR)
- .map(ExecutableElement.class::cast)
- .filter(constructor -> constructor.getModifiers().contains(Modifier.PUBLIC))
- .anyMatch(constructor -> constructor.getParameters().isEmpty())
- || typeElement.getEnclosedElements().stream()
- .noneMatch(elt -> elt.getKind() == ElementKind.CONSTRUCTOR);
-
- if (!hasDefaultConstructor) {
- throw new RuntimeException(
- "The class %s must have a public no-argument constructor that calls super()"
- .formatted(qName));
- }
-
- Optional constructorInfo = Optional.empty();
- if (mainObject) {
- List extends Element> constructorDefs =
- typeElement.getEnclosedElements().stream()
- .filter(elt -> elt.getKind() == ElementKind.CONSTRUCTOR)
- .filter(elt -> !((ExecutableElement) elt).getParameters().isEmpty())
- .toList();
- if (constructorDefs.size() == 1) {
- Element elt = constructorDefs.get(0);
- constructorInfo =
- Optional.of(
- new FunctionInfo(
- "",
- "",
- parseFunctionDescription(elt),
- new TypeInfo(
- ((ExecutableElement) elt).getReturnType().toString(),
- ((ExecutableElement) elt).getReturnType().getKind().name()),
- parseParameters((ExecutableElement) elt)
- .toArray(new ParameterInfo[0])));
- } else if (constructorDefs.size() > 1) {
- // There's more than one non-empty constructor, but Dagger only supports to expose a
- // single one
- throw new RuntimeException(
- "The class %s must have a single non-empty constructor".formatted(qName));
- }
- }
-
- List fieldInfoInfos =
- typeElement.getEnclosedElements().stream()
- .filter(elt -> elt.getKind() == ElementKind.FIELD)
- .filter(elt -> !elt.getModifiers().contains(Modifier.TRANSIENT))
- .filter(elt -> !elt.getModifiers().contains(Modifier.STATIC))
- .filter(elt -> !elt.getModifiers().contains(Modifier.FINAL))
- .filter(
- elt ->
- elt.getModifiers().contains(Modifier.PUBLIC)
- || elt.getAnnotation(Function.class) != null)
- .map(
- elt -> {
- String fieldName = elt.getSimpleName().toString();
- TypeMirror tm = elt.asType();
- TypeKind tk = tm.getKind();
- FieldInfo f =
- new FieldInfo(
- fieldName,
- parseSimpleDescription(elt),
- new TypeInfo(tm.toString(), tk.name()));
- return f;
- })
- .toList();
- List functionInfos =
- typeElement.getEnclosedElements().stream()
- .filter(elt -> elt.getKind() == ElementKind.METHOD)
- .filter(elt -> elt.getAnnotation(Function.class) != null)
- .map(
- elt -> {
- Function moduleFunction = elt.getAnnotation(Function.class);
- String fName = moduleFunction.value();
- String fqName = elt.getSimpleName().toString();
- if (fName.isEmpty()) {
- fName = fqName;
- }
- if (!elt.getModifiers().contains(Modifier.PUBLIC)) {
- throw new RuntimeException(
- "The method %s#%s must be public if annotated with @Function"
- .formatted(qName, fqName));
- }
-
- List parameterInfos =
- parseParameters((ExecutableElement) elt);
-
- TypeMirror tm = ((ExecutableElement) elt).getReturnType();
- TypeKind tk = tm.getKind();
- FunctionInfo functionInfo =
- new FunctionInfo(
- fName,
- fqName,
- parseFunctionDescription(elt),
- new TypeInfo(tm.toString(), tk.name()),
- parameterInfos.toArray(new ParameterInfo[parameterInfos.size()]));
- return functionInfo;
- })
- .toList();
- annotatedObjects.add(
- new ObjectInfo(
- name,
- qName,
- parseObjectDescription(typeElement),
- fieldInfoInfos.toArray(new FieldInfo[fieldInfoInfos.size()]),
- functionInfos.toArray(new FunctionInfo[functionInfos.size()]),
- constructorInfo));
- }
- }
+ private static MethodSpec.Builder invokeFunction(ModuleInfo moduleInfo)
+ throws ClassNotFoundException {
+ var im =
+ MethodSpec.methodBuilder("invoke")
+ .addModifiers(Modifier.PRIVATE)
+ .returns(JSON.class)
+ .addException(Exception.class)
+ .addParameter(JSON.class, "parentJson")
+ .addParameter(String.class, "parentName")
+ .addParameter(String.class, "fnName")
+ .addParameter(
+ ParameterizedTypeName.get(Map.class, String.class, JSON.class), "inputArgs");
+ var firstObj = true;
+ for (var objectInfo : moduleInfo.objects()) {
+ if (firstObj) {
+ firstObj = false;
+ im.beginControlFlow("if (parentName.equals($S))", objectInfo.name());
+ } else {
+ im.nextControlFlow("else if (parentName.equals($S))", objectInfo.name());
}
- }
-
- // Ensure only one single enum is defined with a specific name
- Set enumSimpleNames = new HashSet<>();
- for (var enumQualifiedName : enumInfos.keySet()) {
- String simpleName = enumQualifiedName.substring(enumQualifiedName.lastIndexOf('.') + 1);
- if (enumSimpleNames.contains(simpleName)) {
- throw new RuntimeException(
- "The enum %s has already been registered via %s"
- .formatted(simpleName, enumQualifiedName));
+ // If there's no constructor, we can initialize the main object here as it's the same for
+ // all.
+ // But if there's a constructor we want to inline it under the function branch.
+ if (objectInfo.constructor().isEmpty()) {
+ ClassName objName = ClassName.bestGuess(objectInfo.qualifiedName());
+ im.addStatement("$T clazz = Class.forName($S)", Class.class, objectInfo.qualifiedName())
+ .addStatement(
+ "$T obj = ($T) $T.fromJSON(parentJson, clazz)",
+ objName,
+ objName,
+ JsonConverter.class);
}
- enumSimpleNames.add(simpleName);
- }
-
- return new ModuleInfo(
- moduleDescription,
- annotatedObjects.toArray(new ObjectInfo[annotatedObjects.size()]),
- enumInfos);
- }
-
- private List parseParameters(ExecutableElement elt) {
- return elt.getParameters().stream()
- .filter(param -> !param.asType().toString().equals("io.dagger.client.Client"))
- .map(
- param -> {
- TypeMirror tm = param.asType();
- TypeKind tk = tm.getKind();
-
- boolean isOptional = false;
- var optionalType =
- processingEnv.getElementUtils().getTypeElement(Optional.class.getName()).asType();
- if (tm instanceof DeclaredType dt
- && processingEnv.getTypeUtils().isSameType(dt.asElement().asType(), optionalType)
- && !dt.getTypeArguments().isEmpty()) {
- isOptional = true;
- tm = dt.getTypeArguments().get(0);
- tk = tm.getKind();
- }
-
- Default defaultAnnotation = param.getAnnotation(Default.class);
- var hasDefaultAnnotation = defaultAnnotation != null;
-
- DefaultPath defaultPathAnnotation = param.getAnnotation(DefaultPath.class);
- var hasDefaultPathAnnotation = defaultPathAnnotation != null;
-
- if (hasDefaultPathAnnotation
- && !tm.toString().equals("io.dagger.client.Directory")
- && !tm.toString().equals("io.dagger.client.File")) {
- throw new IllegalArgumentException(
- "Parameter "
- + param.getSimpleName()
- + " cannot have @DefaultPath annotation if it is not a Directory or File type");
- }
-
- if (hasDefaultAnnotation && hasDefaultPathAnnotation) {
- throw new IllegalArgumentException(
- "Parameter "
- + param.getSimpleName()
- + " cannot have both @Default and @DefaultPath annotations");
- }
-
- String defaultValue =
- hasDefaultAnnotation
- ? quoteIfString(defaultAnnotation.value(), tm.toString())
- : null;
-
- String defaultPath = hasDefaultPathAnnotation ? defaultPathAnnotation.value() : null;
-
- Ignore ignoreAnnotation = param.getAnnotation(Ignore.class);
- var hasIgnoreAnnotation = ignoreAnnotation != null;
- if (hasIgnoreAnnotation && !tm.toString().equals("io.dagger.client.Directory")) {
- throw new IllegalArgumentException(
- "Parameter "
- + param.getSimpleName()
- + " cannot have @Ignore annotation if it is not a Directory");
- }
-
- String[] ignoreValue = hasIgnoreAnnotation ? ignoreAnnotation.value() : null;
-
- String paramName = param.getSimpleName().toString();
- return new ParameterInfo(
- paramName,
- parseParameterDescription(elt, paramName),
- new TypeInfo(tm.toString(), tk.name()),
- isOptional,
- Optional.ofNullable(defaultValue),
- Optional.ofNullable(defaultPath),
- Optional.ofNullable(ignoreValue));
- })
- .toList();
- }
-
- static String quoteIfString(String value, String type) {
- if (value == null) {
- return null;
- }
- if (type.equals(String.class.getName())
- && !value.equals("null")
- && (!value.startsWith("\"") && !value.endsWith("\"")
- || !value.startsWith("'") && !value.endsWith("'"))) {
- return "\"" + value.replaceAll("\"", "\\\\\"") + "\"";
- }
- return value;
- }
-
- static JavaFile generate(ModuleInfo moduleInfo) {
- try {
- var rm =
- MethodSpec.methodBuilder("register")
- .addModifiers(Modifier.PRIVATE)
- .returns(ModuleID.class)
- .addException(ExecutionException.class)
- .addException(DaggerQueryException.class)
- .addException(InterruptedException.class)
- .addCode(
- "$T module = $T.dag().module()", io.dagger.client.Module.class, Dagger.class);
- if (isNotBlank(moduleInfo.description())) {
- rm.addCode("\n .withDescription($S)", moduleInfo.description());
- }
- for (var objectInfo : moduleInfo.objects()) {
- rm.addCode("\n .withObject(")
- .addCode("\n $T.dag().typeDef().withObject($S", Dagger.class, objectInfo.name());
- if (isNotBlank(objectInfo.description())) {
- rm.addCode(
- ", new $T.WithObjectArguments().withDescription($S)",
- TypeDef.class,
- objectInfo.description());
- }
- rm.addCode(")"); // end of dag().TypeDef().withObject(
- for (var fnInfo : objectInfo.functions()) {
- rm.addCode("\n .withFunction(")
- .addCode(withFunction(moduleInfo.enumInfos().keySet(), objectInfo, fnInfo))
- .addCode(")"); // end of .withFunction(
- }
- for (var fieldInfo : objectInfo.fields()) {
- rm.addCode("\n .withField(")
- .addCode("$S, ", fieldInfo.name())
- .addCode(DaggerType.of(fieldInfo.type()).toDaggerTypeDef());
- if (isNotBlank(fieldInfo.description())) {
- rm.addCode(", new $T.WithFieldArguments()", io.dagger.client.TypeDef.class)
- .addCode(".withDescription($S)", fieldInfo.description());
- }
- rm.addCode(")");
- }
- if (objectInfo.constructor().isPresent()) {
- rm.addCode("\n .withConstructor(")
- .addCode(
- withFunction(
- moduleInfo.enumInfos().keySet(), objectInfo, objectInfo.constructor().get()))
- .addCode(")"); // end of .withConstructor
- }
- rm.addCode(")"); // end of .withObject(
- }
- for (var enumInfo : moduleInfo.enumInfos().values()) {
- rm.addCode("\n .withEnum(")
- .addCode("\n $T.dag().typeDef().withEnum($S", Dagger.class, enumInfo.name());
- if (isNotBlank(enumInfo.description())) {
- rm.addCode(
- ", new $T.WithEnumArguments().withDescription($S)",
- TypeDef.class,
- enumInfo.description());
- }
- rm.addCode(")"); // end of dag().TypeDef().withEnum(
- for (var enumValue : enumInfo.values()) {
- rm.addCode("\n .withEnumValue($S", enumValue.value());
- if (isNotBlank(enumValue.description())) {
- rm.addCode(
- ", new $T.WithEnumValueArguments().withDescription($S)",
- io.dagger.client.TypeDef.class,
- enumValue.description());
- }
- rm.addCode(")"); // end of .withEnumValue(
+ var firstFn = true;
+ for (var fnInfo : objectInfo.functions()) {
+ if (firstFn) {
+ firstFn = false;
+ im.beginControlFlow("if (fnName.equals($S))", fnInfo.name());
+ } else {
+ im.nextControlFlow("else if (fnName.equals($S))", fnInfo.name());
}
- rm.addCode(")"); // end of .withEnum(
+ im.addCode(functionInvoke(objectInfo, fnInfo));
}
- rm.addCode(";\n") // end of module instantiation
- .addStatement("return module.id()");
- var im =
- MethodSpec.methodBuilder("invoke")
- .addModifiers(Modifier.PRIVATE)
- .returns(JSON.class)
- .addException(Exception.class)
- .addParameter(JSON.class, "parentJson")
- .addParameter(String.class, "parentName")
- .addParameter(String.class, "fnName")
- .addParameter(
- ParameterizedTypeName.get(Map.class, String.class, JSON.class), "inputArgs");
- var firstObj = true;
- for (var objectInfo : moduleInfo.objects()) {
- if (firstObj) {
- firstObj = false;
- im.beginControlFlow("if (parentName.equals($S))", objectInfo.name());
+ if (objectInfo.constructor().isPresent()) {
+ if (firstFn) {
+ firstFn = false;
+ im.beginControlFlow("if (fnName.equals(\"\"))");
} else {
- im.nextControlFlow("else if (parentName.equals($S))", objectInfo.name());
- }
- // If there's no constructor, we can initialize the main object here as it's the same for
- // all.
- // But if there's a constructor we want to inline it under the function branch.
- if (objectInfo.constructor().isEmpty()) {
- ClassName objName = ClassName.bestGuess(objectInfo.qualifiedName());
- im.addStatement("$T clazz = Class.forName($S)", Class.class, objectInfo.qualifiedName())
- .addStatement(
- "$T obj = ($T) $T.fromJSON(parentJson, clazz)",
- objName,
- objName,
- JsonConverter.class);
- }
- var firstFn = true;
- for (var fnInfo : objectInfo.functions()) {
- if (firstFn) {
- firstFn = false;
- im.beginControlFlow("if (fnName.equals($S))", fnInfo.name());
- } else {
- im.nextControlFlow("else if (fnName.equals($S))", fnInfo.name());
- }
- im.addCode(functionInvoke(objectInfo, fnInfo));
- }
-
- if (objectInfo.constructor().isPresent()) {
- if (firstFn) {
- firstFn = false;
- im.beginControlFlow("if (fnName.equals(\"\"))");
- } else {
- im.nextControlFlow("if (fnName.equals(\"\"))");
- }
- im.addCode(functionInvoke(objectInfo, objectInfo.constructor().get()));
+ im.nextControlFlow("else if (fnName.equals(\"\"))");
}
+ im.addCode(functionInvoke(objectInfo, objectInfo.constructor().get()));
+ }
- if (!firstFn) {
- im.endControlFlow(); // functions
- }
+ if (!firstFn) {
+ im.endControlFlow(); // functions
}
- im.endControlFlow() // objects
- .addStatement(
- "throw new $T(new $T(\"unknown function \" + fnName))",
- InvocationTargetException.class,
- java.lang.Error.class);
+ }
+ im.endControlFlow() // objects
+ .addStatement(
+ "throw new $T(new $T(\"unknown function \" + fnName))",
+ InvocationTargetException.class,
+ java.lang.Error.class);
- var f =
- JavaFile.builder(
- "io.dagger.gen.entrypoint",
- TypeSpec.classBuilder("Entrypoint")
- .addModifiers(Modifier.PUBLIC)
- .addMethod(MethodSpec.constructorBuilder().build())
- .addMethod(
- MethodSpec.methodBuilder("main")
- .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
- .addException(Exception.class)
- .returns(void.class)
- .addParameter(String[].class, "args")
- .beginControlFlow("try")
- .addStatement("new Entrypoint().dispatch()")
- .nextControlFlow("finally")
- .addStatement("$T.dag().close()", Dagger.class)
- .endControlFlow()
- .build())
- .addMethod(
- MethodSpec.methodBuilder("dispatch")
- .addModifiers(Modifier.PRIVATE)
- .returns(void.class)
- .addException(Exception.class)
- .addStatement(
- "$T fnCall = $T.dag().currentFunctionCall()",
- FunctionCall.class,
- Dagger.class)
- .beginControlFlow("try")
- .addStatement("$T parentName = fnCall.parentName()", String.class)
- .addStatement("$T fnName = fnCall.name()", String.class)
- .addStatement("$T parentJson = fnCall.parent()", JSON.class)
- .addStatement(
- "$T fnArgs = fnCall.inputArgs()",
- ParameterizedTypeName.get(List.class, FunctionCallArgValue.class))
- .addStatement(
- "$T<$T, $T> inputArgs = new $T<>()",
- Map.class,
- String.class,
- JSON.class,
- HashMap.class)
- .beginControlFlow(
- "for ($T fnArg : fnArgs)", FunctionCallArgValue.class)
- .addStatement("inputArgs.put(fnArg.name(), fnArg.value())")
- .endControlFlow()
- .addCode("\n")
- .addStatement("$T result", JSON.class)
- .beginControlFlow("if (parentName.isEmpty())")
- .addStatement("$T modID = register()", ModuleID.class)
- .addStatement("result = $T.toJSON(modID)", JsonConverter.class)
- .nextControlFlow("else")
- .addStatement(
- "result = invoke(parentJson, parentName, fnName, inputArgs)")
- .endControlFlow()
- .addStatement("fnCall.returnValue(result)")
- .nextControlFlow("catch ($T e)", InvocationTargetException.class)
- .addStatement(
- "fnCall.returnError($T.dag().error(e.getTargetException().getMessage()))",
- Dagger.class)
- .addStatement("throw e")
- .nextControlFlow("catch ($T e)", DaggerExecException.class)
- .addStatement(
- "fnCall.returnError($T.dag().error(e.getMessage())"
- + ".withValue(\"stdout\", $T.toJSON(e.getStdOut()))"
- + ".withValue(\"stderr\", $T.toJSON(e.getStdErr()))"
- + ".withValue(\"cmd\", $T.toJSON(e.getCmd()))"
- + ".withValue(\"exitCode\", $T.toJSON(e.getExitCode()))"
- + ".withValue(\"path\", $T.toJSON(e.getPath())))",
- Dagger.class,
- JsonConverter.class,
- JsonConverter.class,
- JsonConverter.class,
- JsonConverter.class,
- JsonConverter.class)
- .addStatement("throw e")
- .nextControlFlow("catch ($T e)", Exception.class)
- .addStatement(
- "fnCall.returnError($T.dag().error(e.getMessage()))",
- Dagger.class)
- .addStatement("throw e")
- .endControlFlow()
- .build())
- .addMethod(rm.build())
- .addMethod(im.build())
- .build())
- .addFileComment("This class has been generated by dagger-java-sdk. DO NOT EDIT.")
- .indent(" ")
- .addStaticImport(Dagger.class, "dag")
- .build();
+ return im;
+ }
- return f;
+ static JavaFile generate(ModuleInfo moduleInfo) {
+ try {
+ return JavaFile.builder(
+ "io.dagger.gen.entrypoint",
+ TypeSpec.classBuilder("Entrypoint")
+ .addModifiers(Modifier.PUBLIC)
+ .addMethod(MethodSpec.constructorBuilder().build())
+ .addMethod(
+ MethodSpec.methodBuilder("main")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .addException(Exception.class)
+ .returns(void.class)
+ .addParameter(String[].class, "args")
+ .beginControlFlow("try")
+ .addStatement("new Entrypoint().dispatch()")
+ .nextControlFlow("finally")
+ .addStatement("$T.dag().close()", Dagger.class)
+ .endControlFlow()
+ .build())
+ .addMethod(
+ MethodSpec.methodBuilder("dispatch")
+ .addModifiers(Modifier.PRIVATE)
+ .returns(void.class)
+ .addException(Exception.class)
+ .addStatement(
+ "$T fnCall = $T.dag().currentFunctionCall()",
+ FunctionCall.class,
+ Dagger.class)
+ .beginControlFlow("try")
+ .addStatement("$T parentName = fnCall.parentName()", String.class)
+ .addStatement("$T fnName = fnCall.name()", String.class)
+ .addStatement("$T parentJson = fnCall.parent()", JSON.class)
+ .addStatement(
+ "$T fnArgs = fnCall.inputArgs()",
+ ParameterizedTypeName.get(List.class, FunctionCallArgValue.class))
+ .addStatement(
+ "$T<$T, $T> inputArgs = new $T<>()",
+ Map.class,
+ String.class,
+ JSON.class,
+ HashMap.class)
+ .beginControlFlow("for ($T fnArg : fnArgs)", FunctionCallArgValue.class)
+ .addStatement("inputArgs.put(fnArg.name(), fnArg.value())")
+ .endControlFlow()
+ .addCode("\n")
+ .addStatement(
+ "$T result = invoke(parentJson, parentName, fnName, inputArgs)",
+ JSON.class)
+ .addStatement("fnCall.returnValue(result)")
+ .nextControlFlow("catch ($T e)", InvocationTargetException.class)
+ .addStatement(
+ "fnCall.returnError($T.dag().error(e.getTargetException().getMessage()))",
+ Dagger.class)
+ .addStatement("throw e")
+ .nextControlFlow("catch ($T e)", DaggerExecException.class)
+ .addStatement(
+ "fnCall.returnError($T.dag().error(e.getMessage())"
+ + ".withValue(\"stdout\", $T.toJSON(e.getStdOut()))"
+ + ".withValue(\"stderr\", $T.toJSON(e.getStdErr()))"
+ + ".withValue(\"cmd\", $T.toJSON(e.getCmd()))"
+ + ".withValue(\"exitCode\", $T.toJSON(e.getExitCode()))"
+ + ".withValue(\"path\", $T.toJSON(e.getPath())))",
+ Dagger.class,
+ JsonConverter.class,
+ JsonConverter.class,
+ JsonConverter.class,
+ JsonConverter.class,
+ JsonConverter.class)
+ .addStatement("throw e")
+ .nextControlFlow("catch ($T e)", Exception.class)
+ .addStatement(
+ "fnCall.returnError($T.dag().error(e.getMessage()))", Dagger.class)
+ .addStatement("throw e")
+ .endControlFlow()
+ .build())
+ .addMethod(invokeFunction(moduleInfo).build())
+ .build())
+ .addFileComment("This class has been generated by dagger-java-sdk. DO NOT EDIT.")
+ .indent(" ")
+ .addStaticImport(Dagger.class, "dag")
+ .build();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
@@ -681,59 +308,9 @@ private static CodeBlock functionInvoke(ObjectInfo objectInfo, FunctionInfo fnIn
return code.build();
}
- public static CodeBlock withFunction(
- Set enums, ObjectInfo objectInfo, FunctionInfo fnInfo) throws ClassNotFoundException {
- boolean isConstructor = fnInfo.name().equals("");
- CodeBlock.Builder code =
- CodeBlock.builder()
- .add(
- "\n $T.dag().function($S,",
- Dagger.class,
- isConstructor ? "" : fnInfo.name())
- .add("\n ")
- .add(
- isConstructor
- ? DaggerType.of(objectInfo.qualifiedName()).toDaggerTypeDef()
- : DaggerType.of(fnInfo.returnType()).toDaggerTypeDef())
- .add(")");
- if (isNotBlank(fnInfo.description())) {
- code.add("\n .withDescription($S)", fnInfo.description());
- }
- for (var parameterInfo : fnInfo.parameters()) {
- code.add("\n .withArg($S, ", parameterInfo.name())
- .add(DaggerType.of(parameterInfo.type()).toDaggerTypeDef());
- if (parameterInfo.optional()) {
- code.add(".withOptional(true)");
- }
- boolean hasDescription = isNotBlank(parameterInfo.description());
- boolean hasDefaultValue = parameterInfo.defaultValue().isPresent();
- boolean hasDefaultPath = parameterInfo.defaultPath().isPresent();
- boolean hasIgnore = parameterInfo.ignore().isPresent();
- if (hasDescription || hasDefaultValue || hasDefaultPath || hasIgnore) {
- code.add(", new $T.WithArgArguments()", io.dagger.client.Function.class);
- if (hasDescription) {
- code.add(".withDescription($S)", parameterInfo.description());
- }
- if (hasDefaultValue) {
- code.add(
- ".withDefaultValue($T.from($S))", JSON.class, parameterInfo.defaultValue().get());
- }
- if (hasDefaultPath) {
- code.add(".withDefaultPath($S)", parameterInfo.defaultPath().get());
- }
- if (hasIgnore) {
- code.add(".withIgnore(").add(listOf(parameterInfo.ignore().get())).add(")");
- }
- }
- code.add(")");
- }
- return code.build();
- }
-
public static TypeKind getTypeKind(String name) {
try {
- TypeKind kind = TypeKind.valueOf(name);
- return kind;
+ return TypeKind.valueOf(name);
} catch (IllegalArgumentException e) {
return TypeKind.DECLARED;
}
@@ -741,7 +318,8 @@ public static TypeKind getTypeKind(String name) {
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
- ModuleInfo moduleInfo = generateModuleInfo(annotations, roundEnv);
+ ModuleInfo moduleInfo =
+ new ProcessorTools(processingEnv, elementUtils).generateModuleInfo(annotations, roundEnv);
if (moduleInfo.objects().length == 0) {
return true;
@@ -751,7 +329,6 @@ public boolean process(Set extends TypeElement> annotations, RoundEnvironment
try {
JavaFile f = generate(moduleInfo);
-
f.writeTo(processingEnv.getFiler());
} catch (IOException e) {
throw new RuntimeException(e);
@@ -759,84 +336,4 @@ public boolean process(Set extends TypeElement> annotations, RoundEnvironment
return true;
}
-
- private static Boolean isNotBlank(String str) {
- return str != null && !str.isBlank();
- }
-
- private String parseSimpleDescription(Element element) {
- String javadocString = elementUtils.getDocComment(element);
- if (javadocString == null) {
- return "";
- }
- return StaticJavaParser.parseJavadoc(javadocString).getDescription().toText().trim();
- }
-
- private String parseModuleDescription(Element element) {
- Module annotation = element.getAnnotation(Module.class);
- if (annotation != null && !annotation.description().isEmpty()) {
- return annotation.description();
- }
- return parseJavaDocDescription(element);
- }
-
- private String parseObjectDescription(Element element) {
- Object annotation = element.getAnnotation(Object.class);
- if (annotation != null && !annotation.description().isEmpty()) {
- return annotation.description();
- }
- return parseJavaDocDescription(element);
- }
-
- private String parseFunctionDescription(Element element) {
- Function annotation = element.getAnnotation(Function.class);
- if (annotation != null && !annotation.description().isEmpty()) {
- return annotation.description();
- }
- return parseJavaDocDescription(element);
- }
-
- private String parseJavaDocDescription(Element element) {
- String javadocString = elementUtils.getDocComment(element);
- if (javadocString != null) {
- return StaticJavaParser.parseJavadoc(javadocString).getDescription().toText().trim();
- }
- return "";
- }
-
- private String parseParameterDescription(Element element, String paramName) {
- String javadocString = elementUtils.getDocComment(element);
- if (javadocString == null) {
- return "";
- }
- Javadoc javadoc = StaticJavaParser.parseJavadoc(javadocString);
- Optional blockTag =
- javadoc.getBlockTags().stream()
- .filter(tag -> tag.getType() == Type.PARAM)
- .filter(tag -> tag.getName().isPresent() && tag.getName().get().equals(paramName))
- .findFirst();
- return blockTag.map(tag -> tag.getContent().toText()).orElse("");
- }
-
- private static CodeBlock listOf(String[] array) {
- return CodeBlock.builder()
- .add("$T.of(", List.class)
- .add(CodeBlock.join(Arrays.stream(array).map(s -> CodeBlock.of("$S", s)).toList(), ", "))
- .add(")")
- .build();
- }
-
- private static boolean areSimilar(String str1, String str2) {
- return normalize(str1).equals(normalize(str2));
- }
-
- private static String normalize(String str) {
- if (str == null) {
- return "";
- }
- return str.replaceAll("[-_]", " ") // Replace kebab and snake case delimiters with spaces
- .replaceAll("([a-z])([A-Z])", "$1 $2") // Split camel case words
- .toLowerCase(Locale.ROOT) // Convert to lowercase
- .replaceAll("\\s+", ""); // Remove all spaces
- }
}
diff --git a/sdk/java/dagger-java-annotation-processor/src/main/java/io/dagger/annotation/processor/ProcessorTools.java b/sdk/java/dagger-java-annotation-processor/src/main/java/io/dagger/annotation/processor/ProcessorTools.java
new file mode 100644
index 00000000000..d4683f5251c
--- /dev/null
+++ b/sdk/java/dagger-java-annotation-processor/src/main/java/io/dagger/annotation/processor/ProcessorTools.java
@@ -0,0 +1,362 @@
+package io.dagger.annotation.processor;
+
+import com.github.javaparser.StaticJavaParser;
+import com.github.javaparser.javadoc.Javadoc;
+import com.github.javaparser.javadoc.JavadocBlockTag;
+import io.dagger.module.annotation.*;
+import io.dagger.module.annotation.Module;
+import io.dagger.module.annotation.Object;
+import io.dagger.module.info.*;
+import java.util.*;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.RoundEnvironment;
+import javax.lang.model.element.*;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.Elements;
+
+public class ProcessorTools {
+ private final ProcessingEnvironment processingEnv;
+ private final Elements elementUtils;
+
+ ProcessorTools(ProcessingEnvironment processingEnv, Elements elementUtils) {
+ this.processingEnv = processingEnv;
+ this.elementUtils = elementUtils;
+ }
+
+ private String quoteIfString(String value, String type) {
+ if (value == null) {
+ return null;
+ }
+ if (type.equals(String.class.getName())
+ && !value.equals("null")
+ && (!value.startsWith("\"") && !value.endsWith("\"")
+ || !value.startsWith("'") && !value.endsWith("'"))) {
+ return "\"" + value.replaceAll("\"", "\\\\\"") + "\"";
+ }
+ return value;
+ }
+
+ ModuleInfo generateModuleInfo(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ String moduleName = System.getenv("_DAGGER_JAVA_SDK_MODULE_NAME");
+
+ String moduleDescription = null;
+ Set annotatedObjects = new HashSet<>();
+ boolean hasModuleAnnotation = false;
+
+ Map enumInfos = new HashMap<>();
+
+ for (TypeElement annotation : annotations) {
+ for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
+ switch (element.getKind()) {
+ case ENUM -> {
+ if (element.getAnnotation(io.dagger.module.annotation.Enum.class) != null) {
+ String qName = ((TypeElement) element).getQualifiedName().toString();
+ if (!enumInfos.containsKey(qName)) {
+ enumInfos.put(
+ qName,
+ new EnumInfo(
+ element.getSimpleName().toString(),
+ parseJavaDocDescription(element),
+ element.getEnclosedElements().stream()
+ .filter(elt -> elt.getKind() == ElementKind.ENUM_CONSTANT)
+ .map(
+ elt ->
+ new EnumValueInfo(
+ elt.getSimpleName().toString(),
+ parseJavaDocDescription(elt)))
+ .toArray(EnumValueInfo[]::new)));
+ }
+ }
+ }
+ case PACKAGE -> {
+ if (hasModuleAnnotation) {
+ throw new IllegalStateException("Only one @Module annotation is allowed");
+ }
+ hasModuleAnnotation = true;
+ moduleDescription = parseModuleDescription(element);
+ }
+ case CLASS, RECORD -> {
+ TypeElement typeElement = (TypeElement) element;
+ String qName = typeElement.getQualifiedName().toString();
+ String name = typeElement.getAnnotation(Object.class).value();
+ if (name.isEmpty()) {
+ name = typeElement.getSimpleName().toString();
+ }
+
+ boolean mainObject = areSimilar(name, moduleName);
+
+ if (!element.getModifiers().contains(Modifier.PUBLIC)) {
+ throw new RuntimeException(
+ "The class %s must be public if annotated with @Object".formatted(qName));
+ }
+
+ boolean hasDefaultConstructor =
+ typeElement.getEnclosedElements().stream()
+ .filter(elt -> elt.getKind() == ElementKind.CONSTRUCTOR)
+ .map(ExecutableElement.class::cast)
+ .filter(constructor -> constructor.getModifiers().contains(Modifier.PUBLIC))
+ .anyMatch(constructor -> constructor.getParameters().isEmpty())
+ || typeElement.getEnclosedElements().stream()
+ .noneMatch(elt -> elt.getKind() == ElementKind.CONSTRUCTOR);
+
+ if (!hasDefaultConstructor) {
+ throw new RuntimeException(
+ "The class %s must have a public no-argument constructor that calls super()"
+ .formatted(qName));
+ }
+
+ Optional constructorInfo = Optional.empty();
+ if (mainObject) {
+ List extends Element> constructorDefs =
+ typeElement.getEnclosedElements().stream()
+ .filter(elt -> elt.getKind() == ElementKind.CONSTRUCTOR)
+ .filter(elt -> !((ExecutableElement) elt).getParameters().isEmpty())
+ .toList();
+ if (constructorDefs.size() == 1) {
+ Element elt = constructorDefs.get(0);
+ constructorInfo =
+ Optional.of(
+ new FunctionInfo(
+ "",
+ "",
+ parseFunctionDescription(elt),
+ new TypeInfo(
+ ((ExecutableElement) elt).getReturnType().toString(),
+ ((ExecutableElement) elt).getReturnType().getKind().name()),
+ parseParameters((ExecutableElement) elt)
+ .toArray(new ParameterInfo[0])));
+ } else if (constructorDefs.size() > 1) {
+ // There's more than one non-empty constructor, but Dagger only supports to expose a
+ // single one
+ throw new RuntimeException(
+ "The class %s must have a single non-empty constructor".formatted(qName));
+ }
+ }
+
+ List fieldInfoInfos =
+ typeElement.getEnclosedElements().stream()
+ .filter(elt -> elt.getKind() == ElementKind.FIELD)
+ .filter(elt -> !elt.getModifiers().contains(Modifier.TRANSIENT))
+ .filter(elt -> !elt.getModifiers().contains(Modifier.STATIC))
+ .filter(elt -> !elt.getModifiers().contains(Modifier.FINAL))
+ .filter(
+ elt ->
+ elt.getModifiers().contains(Modifier.PUBLIC)
+ || elt.getAnnotation(Function.class) != null)
+ .map(
+ elt -> {
+ String fieldName = elt.getSimpleName().toString();
+ TypeMirror tm = elt.asType();
+ TypeKind tk = tm.getKind();
+ return new FieldInfo(
+ fieldName,
+ parseSimpleDescription(elt),
+ new TypeInfo(tm.toString(), tk.name()));
+ })
+ .toList();
+ List functionInfos =
+ typeElement.getEnclosedElements().stream()
+ .filter(elt -> elt.getKind() == ElementKind.METHOD)
+ .filter(elt -> elt.getAnnotation(Function.class) != null)
+ .map(
+ elt -> {
+ Function moduleFunction = elt.getAnnotation(Function.class);
+ String fName = moduleFunction.value();
+ String fqName = elt.getSimpleName().toString();
+ if (fName.isEmpty()) {
+ fName = fqName;
+ }
+ if (!elt.getModifiers().contains(Modifier.PUBLIC)) {
+ throw new RuntimeException(
+ "The method %s#%s must be public if annotated with @Function"
+ .formatted(qName, fqName));
+ }
+
+ List parameterInfos =
+ parseParameters((ExecutableElement) elt);
+
+ TypeMirror tm = ((ExecutableElement) elt).getReturnType();
+ TypeKind tk = tm.getKind();
+ return new FunctionInfo(
+ fName,
+ fqName,
+ parseFunctionDescription(elt),
+ new TypeInfo(tm.toString(), tk.name()),
+ parameterInfos.toArray(new ParameterInfo[0]));
+ })
+ .toList();
+ annotatedObjects.add(
+ new ObjectInfo(
+ name,
+ qName,
+ parseObjectDescription(typeElement),
+ fieldInfoInfos.toArray(new FieldInfo[0]),
+ functionInfos.toArray(new FunctionInfo[0]),
+ constructorInfo));
+ }
+ }
+ }
+ }
+
+ // Ensure only one single enum is defined with a specific name
+ Set enumSimpleNames = new HashSet<>();
+ for (var enumQualifiedName : enumInfos.keySet()) {
+ String simpleName = enumQualifiedName.substring(enumQualifiedName.lastIndexOf('.') + 1);
+ if (enumSimpleNames.contains(simpleName)) {
+ throw new RuntimeException(
+ "The enum %s has already been registered via %s"
+ .formatted(simpleName, enumQualifiedName));
+ }
+ enumSimpleNames.add(simpleName);
+ }
+
+ return new ModuleInfo(
+ moduleDescription, annotatedObjects.toArray(new ObjectInfo[0]), enumInfos);
+ }
+
+ List parseParameters(ExecutableElement elt) {
+ return elt.getParameters().stream()
+ .filter(param -> !param.asType().toString().equals("io.dagger.client.Client"))
+ .map(
+ param -> {
+ TypeMirror tm = param.asType();
+ TypeKind tk = tm.getKind();
+
+ boolean isOptional = false;
+ var optionalType =
+ processingEnv.getElementUtils().getTypeElement(Optional.class.getName()).asType();
+ if (tm instanceof DeclaredType dt
+ && processingEnv.getTypeUtils().isSameType(dt.asElement().asType(), optionalType)
+ && !dt.getTypeArguments().isEmpty()) {
+ isOptional = true;
+ tm = dt.getTypeArguments().get(0);
+ tk = tm.getKind();
+ }
+
+ Default defaultAnnotation = param.getAnnotation(Default.class);
+ var hasDefaultAnnotation = defaultAnnotation != null;
+
+ DefaultPath defaultPathAnnotation = param.getAnnotation(DefaultPath.class);
+ var hasDefaultPathAnnotation = defaultPathAnnotation != null;
+
+ if (hasDefaultPathAnnotation
+ && !tm.toString().equals("io.dagger.client.Directory")
+ && !tm.toString().equals("io.dagger.client.File")) {
+ throw new IllegalArgumentException(
+ "Parameter "
+ + param.getSimpleName()
+ + " cannot have @DefaultPath annotation if it is not a Directory or File type");
+ }
+
+ if (hasDefaultAnnotation && hasDefaultPathAnnotation) {
+ throw new IllegalArgumentException(
+ "Parameter "
+ + param.getSimpleName()
+ + " cannot have both @Default and @DefaultPath annotations");
+ }
+
+ String defaultValue =
+ hasDefaultAnnotation
+ ? quoteIfString(defaultAnnotation.value(), tm.toString())
+ : null;
+
+ String defaultPath = hasDefaultPathAnnotation ? defaultPathAnnotation.value() : null;
+
+ Ignore ignoreAnnotation = param.getAnnotation(Ignore.class);
+ var hasIgnoreAnnotation = ignoreAnnotation != null;
+ if (hasIgnoreAnnotation && !tm.toString().equals("io.dagger.client.Directory")) {
+ throw new IllegalArgumentException(
+ "Parameter "
+ + param.getSimpleName()
+ + " cannot have @Ignore annotation if it is not a Directory");
+ }
+
+ String[] ignoreValue = hasIgnoreAnnotation ? ignoreAnnotation.value() : null;
+
+ String paramName = param.getSimpleName().toString();
+ return new ParameterInfo(
+ paramName,
+ parseParameterDescription(elt, paramName),
+ new TypeInfo(tm.toString(), tk.name()),
+ isOptional,
+ Optional.ofNullable(defaultValue),
+ Optional.ofNullable(defaultPath),
+ Optional.ofNullable(ignoreValue));
+ })
+ .toList();
+ }
+
+ static Boolean isNotBlank(String str) {
+ return str != null && !str.isBlank();
+ }
+
+ private String parseSimpleDescription(Element element) {
+ String javadocString = elementUtils.getDocComment(element);
+ if (javadocString == null) {
+ return "";
+ }
+ return StaticJavaParser.parseJavadoc(javadocString).getDescription().toText().trim();
+ }
+
+ private String parseModuleDescription(Element element) {
+ io.dagger.module.annotation.Module annotation = element.getAnnotation(Module.class);
+ if (annotation != null && !annotation.description().isEmpty()) {
+ return annotation.description();
+ }
+ return parseJavaDocDescription(element);
+ }
+
+ private String parseObjectDescription(Element element) {
+ Object annotation = element.getAnnotation(Object.class);
+ if (annotation != null && !annotation.description().isEmpty()) {
+ return annotation.description();
+ }
+ return parseJavaDocDescription(element);
+ }
+
+ private String parseFunctionDescription(Element element) {
+ Function annotation = element.getAnnotation(Function.class);
+ if (annotation != null && !annotation.description().isEmpty()) {
+ return annotation.description();
+ }
+ return parseJavaDocDescription(element);
+ }
+
+ private String parseJavaDocDescription(Element element) {
+ String javadocString = elementUtils.getDocComment(element);
+ if (javadocString != null) {
+ return StaticJavaParser.parseJavadoc(javadocString).getDescription().toText().trim();
+ }
+ return "";
+ }
+
+ private String parseParameterDescription(Element element, String paramName) {
+ String javadocString = elementUtils.getDocComment(element);
+ if (javadocString == null) {
+ return "";
+ }
+ Javadoc javadoc = StaticJavaParser.parseJavadoc(javadocString);
+ Optional blockTag =
+ javadoc.getBlockTags().stream()
+ .filter(tag -> tag.getType() == JavadocBlockTag.Type.PARAM)
+ .filter(tag -> tag.getName().isPresent() && tag.getName().get().equals(paramName))
+ .findFirst();
+ return blockTag.map(tag -> tag.getContent().toText()).orElse("");
+ }
+
+ private static boolean areSimilar(String str1, String str2) {
+ return normalize(str1).equals(normalize(str2));
+ }
+
+ private static String normalize(String str) {
+ if (str == null) {
+ return "";
+ }
+ return str.replaceAll("[-_]", " ") // Replace kebab and snake case delimiters with spaces
+ .replaceAll("([a-z])([A-Z])", "$1 $2") // Split camel case words
+ .toLowerCase(Locale.ROOT) // Convert to lowercase
+ .replaceAll("\\s+", ""); // Remove all spaces
+ }
+}
diff --git a/sdk/java/dagger-java-annotation-processor/src/main/java/io/dagger/annotation/processor/TypeDefs.java b/sdk/java/dagger-java-annotation-processor/src/main/java/io/dagger/annotation/processor/TypeDefs.java
new file mode 100644
index 00000000000..8c12bfea48e
--- /dev/null
+++ b/sdk/java/dagger-java-annotation-processor/src/main/java/io/dagger/annotation/processor/TypeDefs.java
@@ -0,0 +1,266 @@
+package io.dagger.annotation.processor;
+
+import com.google.auto.service.AutoService;
+import com.palantir.javapoet.*;
+import io.dagger.client.*;
+import io.dagger.client.exception.DaggerExecException;
+import io.dagger.client.exception.DaggerQueryException;
+import io.dagger.module.info.FunctionInfo;
+import io.dagger.module.info.ModuleInfo;
+import io.dagger.module.info.ObjectInfo;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.*;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.annotation.processing.Processor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.*;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+
+@SupportedAnnotationTypes({
+ "io.dagger.module.annotation.Module",
+ "io.dagger.module.annotation.Object",
+ "io.dagger.module.annotation.Enum",
+ "io.dagger.module.annotation.Function",
+ "io.dagger.module.annotation.Optional",
+ "io.dagger.module.annotation.Default",
+ "io.dagger.module.annotation.DefaultPath"
+})
+@SupportedSourceVersion(SourceVersion.RELEASE_17)
+@AutoService(Processor.class)
+public class TypeDefs extends AbstractProcessor {
+
+ private Elements elementUtils;
+
+ @Override
+ public synchronized void init(ProcessingEnvironment processingEnv) {
+ super.init(processingEnv);
+ this.elementUtils = processingEnv.getElementUtils(); // Récupération d'Elements
+ }
+
+ private static MethodSpec.Builder registerFunction(ModuleInfo moduleInfo)
+ throws ClassNotFoundException {
+ var rm =
+ MethodSpec.methodBuilder("register")
+ .addModifiers(Modifier.STATIC)
+ .returns(ModuleID.class)
+ .addException(ExecutionException.class)
+ .addException(DaggerQueryException.class)
+ .addException(InterruptedException.class)
+ .addCode("$T module = $T.dag().module()", io.dagger.client.Module.class, Dagger.class);
+ if (ProcessorTools.isNotBlank(moduleInfo.description())) {
+ rm.addCode("\n .withDescription($S)", moduleInfo.description());
+ }
+ for (var objectInfo : moduleInfo.objects()) {
+ rm.addCode("\n .withObject(")
+ .addCode("\n $T.dag().typeDef().withObject($S", Dagger.class, objectInfo.name());
+ if (ProcessorTools.isNotBlank(objectInfo.description())) {
+ rm.addCode(
+ ", new $T.WithObjectArguments().withDescription($S)",
+ TypeDef.class,
+ objectInfo.description());
+ }
+ rm.addCode(")"); // end of dag().TypeDef().withObject(
+ for (var fnInfo : objectInfo.functions()) {
+ rm.addCode("\n .withFunction(")
+ .addCode(withFunction(objectInfo, fnInfo))
+ .addCode(")"); // end of .withFunction(
+ }
+ for (var fieldInfo : objectInfo.fields()) {
+ rm.addCode("\n .withField(")
+ .addCode("$S, ", fieldInfo.name())
+ .addCode(DaggerType.of(fieldInfo.type()).toDaggerTypeDef());
+ if (ProcessorTools.isNotBlank(fieldInfo.description())) {
+ rm.addCode(", new $T.WithFieldArguments()", TypeDef.class)
+ .addCode(".withDescription($S)", fieldInfo.description());
+ }
+ rm.addCode(")");
+ }
+ if (objectInfo.constructor().isPresent()) {
+ rm.addCode("\n .withConstructor(")
+ .addCode(withFunction(objectInfo, objectInfo.constructor().get()))
+ .addCode(")"); // end of .withConstructor
+ }
+ rm.addCode(")"); // end of .withObject(
+ }
+ for (var enumInfo : moduleInfo.enumInfos().values()) {
+ rm.addCode("\n .withEnum(")
+ .addCode("\n $T.dag().typeDef().withEnum($S", Dagger.class, enumInfo.name());
+ if (ProcessorTools.isNotBlank(enumInfo.description())) {
+ rm.addCode(
+ ", new $T.WithEnumArguments().withDescription($S)",
+ TypeDef.class,
+ enumInfo.description());
+ }
+ rm.addCode(")"); // end of dag().TypeDef().withEnum(
+ for (var enumValue : enumInfo.values()) {
+ rm.addCode("\n .withEnumValue($S", enumValue.value());
+ if (ProcessorTools.isNotBlank(enumValue.description())) {
+ rm.addCode(
+ ", new $T.WithEnumValueArguments().withDescription($S)",
+ TypeDef.class,
+ enumValue.description());
+ }
+ rm.addCode(")"); // end of .withEnumValue(
+ }
+ rm.addCode(")"); // end of .withEnum(
+ }
+ rm.addCode(";\n") // end of module instantiation
+ .addStatement("return module.id()");
+
+ return rm;
+ }
+
+ static JavaFile generateRegister(ModuleInfo moduleInfo) {
+ try {
+ return JavaFile.builder(
+ "io.dagger.gen.entrypoint",
+ TypeSpec.classBuilder("TypeDefs")
+ .addModifiers(Modifier.PUBLIC)
+ .addMethod(MethodSpec.constructorBuilder().build())
+ .addMethod(
+ MethodSpec.methodBuilder("main")
+ .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
+ .addException(Exception.class)
+ .returns(void.class)
+ .addParameter(String[].class, "args")
+ .beginControlFlow("try")
+ .addStatement("new TypeDefs().dispatch()")
+ .nextControlFlow("finally")
+ .addStatement("$T.dag().close()", Dagger.class)
+ .endControlFlow()
+ .build())
+ .addMethod(
+ MethodSpec.methodBuilder("dispatch")
+ .addModifiers(Modifier.PRIVATE)
+ .returns(void.class)
+ .addException(Exception.class)
+ .addStatement(
+ "$T fnCall = $T.dag().currentFunctionCall()",
+ FunctionCall.class,
+ Dagger.class)
+ .beginControlFlow("try")
+ .addStatement(
+ "fnCall.returnValue($T.toJSON(register()))", JsonConverter.class)
+ .nextControlFlow("catch ($T e)", InvocationTargetException.class)
+ .addStatement(
+ "fnCall.returnError($T.dag().error(e.getTargetException().getMessage()))",
+ Dagger.class)
+ .addStatement("throw e")
+ .nextControlFlow("catch ($T e)", DaggerExecException.class)
+ .addStatement(
+ "fnCall.returnError($T.dag().error(e.getMessage())"
+ + ".withValue(\"stdout\", $T.toJSON(e.getStdOut()))"
+ + ".withValue(\"stderr\", $T.toJSON(e.getStdErr()))"
+ + ".withValue(\"cmd\", $T.toJSON(e.getCmd()))"
+ + ".withValue(\"exitCode\", $T.toJSON(e.getExitCode()))"
+ + ".withValue(\"path\", $T.toJSON(e.getPath())))",
+ Dagger.class,
+ JsonConverter.class,
+ JsonConverter.class,
+ JsonConverter.class,
+ JsonConverter.class,
+ JsonConverter.class)
+ .addStatement("throw e")
+ .nextControlFlow("catch ($T e)", Exception.class)
+ .addStatement(
+ "fnCall.returnError($T.dag().error(e.getMessage()))", Dagger.class)
+ .addStatement("throw e")
+ .endControlFlow()
+ .build())
+ .addMethod(registerFunction(moduleInfo).build())
+ .build())
+ .addFileComment("This class has been generated by dagger-java-sdk. DO NOT EDIT.")
+ .indent(" ")
+ .addStaticImport(Dagger.class, "dag")
+ .build();
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static CodeBlock withFunction(ObjectInfo objectInfo, FunctionInfo fnInfo) {
+ boolean isConstructor = fnInfo.name().equals("");
+ CodeBlock.Builder code =
+ CodeBlock.builder()
+ .add(
+ "\n $T.dag().function($S,",
+ Dagger.class,
+ isConstructor ? "" : fnInfo.name())
+ .add("\n ")
+ .add(
+ isConstructor
+ ? DaggerType.of(objectInfo.qualifiedName()).toDaggerTypeDef()
+ : DaggerType.of(fnInfo.returnType()).toDaggerTypeDef())
+ .add(")");
+ if (ProcessorTools.isNotBlank(fnInfo.description())) {
+ code.add("\n .withDescription($S)", fnInfo.description());
+ }
+ for (var parameterInfo : fnInfo.parameters()) {
+ code.add("\n .withArg($S, ", parameterInfo.name())
+ .add(DaggerType.of(parameterInfo.type()).toDaggerTypeDef());
+ if (parameterInfo.optional()) {
+ code.add(".withOptional(true)");
+ }
+ boolean hasDescription = ProcessorTools.isNotBlank(parameterInfo.description());
+ boolean hasDefaultValue = parameterInfo.defaultValue().isPresent();
+ boolean hasDefaultPath = parameterInfo.defaultPath().isPresent();
+ boolean hasIgnore = parameterInfo.ignore().isPresent();
+ if (hasDescription || hasDefaultValue || hasDefaultPath || hasIgnore) {
+ code.add(", new $T.WithArgArguments()", io.dagger.client.Function.class);
+ if (hasDescription) {
+ code.add(".withDescription($S)", parameterInfo.description());
+ }
+ if (hasDefaultValue) {
+ code.add(
+ ".withDefaultValue($T.from($S))", JSON.class, parameterInfo.defaultValue().get());
+ }
+ if (hasDefaultPath) {
+ code.add(".withDefaultPath($S)", parameterInfo.defaultPath().get());
+ }
+ if (hasIgnore) {
+ code.add(".withIgnore(").add(listOf(parameterInfo.ignore().get())).add(")");
+ }
+ }
+ code.add(")");
+ }
+ return code.build();
+ }
+
+ @Override
+ public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ ModuleInfo moduleInfo =
+ new ProcessorTools(processingEnv, elementUtils).generateModuleInfo(annotations, roundEnv);
+
+ if (moduleInfo.objects().length == 0) {
+ return true;
+ }
+
+ DaggerType.setKnownEnums(moduleInfo.enumInfos().keySet());
+
+ try {
+ JavaFile f = generateRegister(moduleInfo);
+ f.writeTo(processingEnv.getFiler());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ return true;
+ }
+
+ private static CodeBlock listOf(String[] array) {
+ return CodeBlock.builder()
+ .add("$T.of(", List.class)
+ .add(CodeBlock.join(Arrays.stream(array).map(s -> CodeBlock.of("$S", s)).toList(), ", "))
+ .add(")")
+ .build();
+ }
+}
diff --git a/sdk/java/dagger-java-annotation-processor/src/test/java/io/dagger/annotation/processor/GenerateTest.java b/sdk/java/dagger-java-annotation-processor/src/test/java/io/dagger/annotation/processor/GenerateTest.java
index 1f84a8bde43..d2fce7e514e 100644
--- a/sdk/java/dagger-java-annotation-processor/src/test/java/io/dagger/annotation/processor/GenerateTest.java
+++ b/sdk/java/dagger-java-annotation-processor/src/test/java/io/dagger/annotation/processor/GenerateTest.java
@@ -10,7 +10,7 @@
public class GenerateTest {
@Test
- public void testAnnotationGeneration() throws Exception {
+ public void testRuntimeGeneration() throws Exception {
new EnvironmentVariables("_DAGGER_JAVA_SDK_MODULE_NAME", "dagger-java")
.execute(
() -> {
@@ -27,4 +27,23 @@ public void testAnnotationGeneration() throws Exception {
JavaFileObjects.forResource("io/dagger/gen/entrypoint/Entrypoint.java"));
});
}
+
+ @Test
+ public void testTypeDefsGeneration() throws Exception {
+ new EnvironmentVariables("_DAGGER_JAVA_SDK_MODULE_NAME", "dagger-java")
+ .execute(
+ () -> {
+ Compilation compilation =
+ javac()
+ .withProcessors(new TypeDefs())
+ .compile(
+ JavaFileObjects.forResource("io/dagger/java/module/DaggerJava.java"),
+ JavaFileObjects.forResource("io/dagger/java/module/package-info.java"));
+ assertThat(compilation).succeeded();
+ assertThat(compilation)
+ .generatedSourceFile("io.dagger.gen.entrypoint.TypeDefs")
+ .hasSourceEquivalentTo(
+ JavaFileObjects.forResource("io/dagger/gen/entrypoint/typedefs.java"));
+ });
+ }
}
diff --git a/sdk/java/dagger-java-annotation-processor/src/test/resources/io/dagger/gen/entrypoint/Entrypoint.java b/sdk/java/dagger-java-annotation-processor/src/test/resources/io/dagger/gen/entrypoint/Entrypoint.java
index 7836fa81120..50b741b8507 100644
--- a/sdk/java/dagger-java-annotation-processor/src/test/resources/io/dagger/gen/entrypoint/Entrypoint.java
+++ b/sdk/java/dagger-java-annotation-processor/src/test/resources/io/dagger/gen/entrypoint/Entrypoint.java
@@ -1,40 +1,33 @@
// This class has been generated by dagger-java-sdk. DO NOT EDIT.
package io.dagger.gen.entrypoint;
- import static io.dagger.client.Dagger.dag;
+import static io.dagger.client.Dagger.dag;
- import io.dagger.client.Container;
- import io.dagger.client.Directory;
- import io.dagger.client.Function;
- import io.dagger.client.FunctionCall;
- import io.dagger.client.FunctionCallArgValue;
- import io.dagger.client.JSON;
- import io.dagger.client.JsonConverter;
- import io.dagger.client.Module;
- import io.dagger.client.ModuleID;
- import io.dagger.client.Platform;
- import io.dagger.client.TypeDef;
- import io.dagger.client.TypeDefKind;
- import io.dagger.client.exception.DaggerExecException;
- import io.dagger.client.exception.DaggerQueryException;
- import io.dagger.java.module.DaggerJava;
- import java.lang.Class;
- import java.lang.Error;
- import java.lang.Exception;
- import java.lang.Integer;
- import java.lang.InterruptedException;
- import java.lang.String;
- import java.lang.reflect.InvocationTargetException;
- import java.util.Arrays;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.Objects;
- import java.util.Optional;
- import java.util.concurrent.ExecutionException;
+import io.dagger.client.Container;
+import io.dagger.client.Directory;
+import io.dagger.client.FunctionCall;
+import io.dagger.client.FunctionCallArgValue;
+import io.dagger.client.JSON;
+import io.dagger.client.JsonConverter;
+import io.dagger.client.Platform;
+import io.dagger.client.exception.DaggerExecException;
+import io.dagger.java.module.DaggerJava;
+import java.lang.Class;
+import java.lang.Error;
+import java.lang.Exception;
+import java.lang.Integer;
+import java.lang.String;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
public class Entrypoint {
- Entrypoint() {}
+ Entrypoint() {
+ }
public static void main(String[] args) throws Exception {
try {
@@ -56,13 +49,7 @@ private void dispatch() throws Exception {
inputArgs.put(fnArg.name(), fnArg.value());
}
- JSON result;
- if (parentName.isEmpty()) {
- ModuleID modID = register();
- result = JsonConverter.toJSON(modID);
- } else {
- result = invoke(parentJson, parentName, fnName, inputArgs);
- }
+ JSON result = invoke(parentJson, parentName, fnName, inputArgs);
fnCall.returnValue(result);
} catch (InvocationTargetException e) {
fnCall.returnError(dag().error(e.getTargetException().getMessage()));
@@ -76,96 +63,6 @@ private void dispatch() throws Exception {
}
}
- private ModuleID register()
- throws ExecutionException, DaggerQueryException, InterruptedException {
- Module module = dag().module().withDescription("Dagger Java Module example").withObject(dag()
- .typeDef()
- .withObject("DaggerJava",
- new TypeDef.WithObjectArguments().withDescription("Dagger Java Module main object"))
- .withFunction(dag().function("containerEcho", dag().typeDef().withObject("Container"))
- .withDescription("Returns a container that echoes whatever string argument is provided")
- .withArg("stringArg", dag().typeDef().withKind(TypeDefKind.STRING_KIND),
- new Function.WithArgArguments().withDescription("string to echo")
- .withDefaultValue(JSON.from("\"Hello Dagger\""))))
- .withFunction(dag().function("grepDir", dag().typeDef().withKind(TypeDefKind.STRING_KIND))
- .withDescription(
- "Returns lines that match a pattern in the files of the provided Directory")
- .withArg("directoryArg", dag().typeDef().withObject("Directory"),
- new Function.WithArgArguments().withDescription("Directory to grep")
- .withDefaultPath("sdk/java").withIgnore(List.of("**", "!*.java")))
- .withArg("pattern",
- dag().typeDef().withKind(TypeDefKind.STRING_KIND).withOptional(true),
- new Function.WithArgArguments()
- .withDescription("Pattern to search for in the directory")))
- .withFunction(dag().function("itself", dag().typeDef().withObject("DaggerJava")))
- .withFunction(dag().function("isZero", dag().typeDef().withKind(TypeDefKind.BOOLEAN_KIND))
- .withDescription("but this description should be exposed").withArg("value",
- dag().typeDef().withKind(TypeDefKind.INTEGER_KIND)))
- .withFunction(dag()
- .function("doThings",
- dag().typeDef().withListOf(dag().typeDef().withKind(TypeDefKind.INTEGER_KIND)))
- .withArg("stringArray",
- dag().typeDef().withListOf(dag().typeDef().withKind(TypeDefKind.STRING_KIND)))
- .withArg("ints",
- dag().typeDef().withListOf(dag().typeDef().withKind(TypeDefKind.INTEGER_KIND)))
- .withArg("containers",
- dag().typeDef().withListOf(dag().typeDef().withObject("Container"))))
- .withFunction(dag()
- .function("nonNullableNoDefault", dag().typeDef().withKind(TypeDefKind.STRING_KIND))
- .withDescription("User must provide the argument")
- .withArg("stringArg", dag().typeDef().withKind(TypeDefKind.STRING_KIND)))
- .withFunction(dag()
- .function("nonNullableDefault", dag().typeDef().withKind(TypeDefKind.STRING_KIND))
- .withDescription(
- "If the user doesn't provide an argument, a default value is used. The argument can't be null.")
- .withArg("stringArg", dag().typeDef().withKind(TypeDefKind.STRING_KIND),
- new Function.WithArgArguments().withDefaultValue(JSON.from("\"default value\""))))
- .withFunction(dag().function("nullable", dag().typeDef().withKind(TypeDefKind.STRING_KIND))
- .withDescription(
- "Make it optional but do not define a value. If the user doesn't provide an argument, it will be\n"
- + " set to null.")
- .withArg("stringArg",
- dag().typeDef().withKind(TypeDefKind.STRING_KIND).withOptional(true)))
- .withFunction(dag()
- .function("nullableDefault", dag().typeDef().withKind(TypeDefKind.STRING_KIND))
- .withDescription(
- "Set a default value in case the user doesn't provide a value and allow for null value.")
- .withArg("stringArg",
- dag().typeDef().withKind(TypeDefKind.STRING_KIND).withOptional(true),
- new Function.WithArgArguments().withDefaultValue(JSON.from("\"Foo\""))))
- .withFunction(dag().function("defaultPlatform", dag().typeDef().withScalar("Platform"))
- .withDescription("return the default platform as a Scalar value"))
- .withFunction(dag().function("addFloat", dag().typeDef().withKind(TypeDefKind.FLOAT_KIND))
- .withArg("a", dag().typeDef().withKind(TypeDefKind.FLOAT_KIND))
- .withArg("b", dag().typeDef().withKind(TypeDefKind.FLOAT_KIND)))
- .withFunction(dag()
- .function("doSomething",
- dag().typeDef().withKind(TypeDefKind.VOID_KIND).withOptional(true))
- .withDescription("Function returning nothing")
- .withArg("src", dag().typeDef().withObject("Directory")))
- .withFunction(
- dag().function("printSeverity", dag().typeDef().withKind(TypeDefKind.STRING_KIND))
- .withArg("severity", dag().typeDef().withEnum("Severity")))
- .withField("source", dag().typeDef().withObject("Directory"),
- new TypeDef.WithFieldArguments().withDescription("Project source directory"))
- .withField("version", dag().typeDef().withKind(TypeDefKind.STRING_KIND))
- .withConstructor(dag().function("", dag().typeDef().withObject("DaggerJava"))
- .withDescription("Initialize the DaggerJava Module")
- .withArg("source", dag().typeDef().withObject("Directory").withOptional(true),
- new Function.WithArgArguments().withDescription("Project source directory"))
- .withArg("version", dag().typeDef().withKind(TypeDefKind.STRING_KIND),
- new Function.WithArgArguments().withDescription("Go version")
- .withDefaultValue(JSON.from("\"1.23.2\"")))))
- .withEnum(dag().typeDef()
- .withEnum("Severity", new TypeDef.WithEnumArguments().withDescription("Severities"))
- .withEnumValue("DEBUG",
- new TypeDef.WithEnumValueArguments().withDescription("Debug severity"))
- .withEnumValue("INFO",
- new TypeDef.WithEnumValueArguments().withDescription("Info severity"))
- .withEnumValue("WARN").withEnumValue("ERROR").withEnumValue("FATAL"));
- return module.id();
- }
-
private JSON invoke(JSON parentJson, String parentName, String fnName,
Map inputArgs) throws Exception {
if (parentName.equals("DaggerJava")) {
@@ -223,8 +120,7 @@ private JSON invoke(JSON parentJson, String parentName, String fnName,
Objects.requireNonNull(ints, "ints must not be null");
List containers = null;
if (inputArgs.get("containers") != null) {
- containers =
- Arrays.asList(JsonConverter.fromJSON(inputArgs.get("containers"), Container[].class));
+ containers = Arrays.asList(JsonConverter.fromJSON(inputArgs.get("containers"), Container[].class));
}
Objects.requireNonNull(containers, "containers must not be null");
int[] res = obj.doThings(stringArray, ints, containers);
@@ -307,8 +203,7 @@ private JSON invoke(JSON parentJson, String parentName, String fnName,
Objects.requireNonNull(severity, "severity must not be null");
String res = obj.printSeverity(severity);
return JsonConverter.toJSON(res);
- }
- if (fnName.equals("")) {
+ } else if (fnName.equals("")) {
Directory source = null;
if (inputArgs.get("source") != null) {
source = JsonConverter.fromJSON(inputArgs.get("source"), Directory.class);
@@ -325,4 +220,4 @@ private JSON invoke(JSON parentJson, String parentName, String fnName,
}
throw new InvocationTargetException(new Error("unknown function " + fnName));
}
-}
+}
\ No newline at end of file
diff --git a/sdk/java/dagger-java-annotation-processor/src/test/resources/io/dagger/gen/entrypoint/typedefs.java b/sdk/java/dagger-java-annotation-processor/src/test/resources/io/dagger/gen/entrypoint/typedefs.java
new file mode 100644
index 00000000000..0a34a0747f6
--- /dev/null
+++ b/sdk/java/dagger-java-annotation-processor/src/test/resources/io/dagger/gen/entrypoint/typedefs.java
@@ -0,0 +1,137 @@
+// This class has been generated by dagger-java-sdk. DO NOT EDIT.
+package io.dagger.gen.entrypoint;
+
+import static io.dagger.client.Dagger.dag;
+
+import io.dagger.client.Function;
+import io.dagger.client.FunctionCall;
+import io.dagger.client.JSON;
+import io.dagger.client.JsonConverter;
+import io.dagger.client.Module;
+import io.dagger.client.ModuleID;
+import io.dagger.client.TypeDef;
+import io.dagger.client.TypeDefKind;
+import io.dagger.client.exception.DaggerExecException;
+import io.dagger.client.exception.DaggerQueryException;
+import java.lang.Exception;
+import java.lang.InterruptedException;
+import java.lang.String;
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+public class TypeDefs {
+ TypeDefs() {
+ }
+
+ public static void main(String[] args) throws Exception {
+ try {
+ new TypeDefs().dispatch();
+ } finally {
+ dag().close();
+ }
+ }
+
+ private void dispatch() throws Exception {
+ FunctionCall fnCall = dag().currentFunctionCall();
+ try {
+ fnCall.returnValue(JsonConverter.toJSON(register()));
+ } catch (InvocationTargetException e) {
+ fnCall.returnError(dag().error(e.getTargetException().getMessage()));
+ throw e;
+ } catch (DaggerExecException e) {
+ fnCall.returnError(dag().error(e.getMessage()).withValue("stdout", JsonConverter.toJSON(e.getStdOut())).withValue("stderr", JsonConverter.toJSON(e.getStdErr())).withValue("cmd", JsonConverter.toJSON(e.getCmd())).withValue("exitCode", JsonConverter.toJSON(e.getExitCode())).withValue("path", JsonConverter.toJSON(e.getPath())));
+ throw e;
+ } catch (Exception e) {
+ fnCall.returnError(dag().error(e.getMessage()));
+ throw e;
+ }
+ }
+
+ static ModuleID register() throws ExecutionException, DaggerQueryException, InterruptedException {
+ Module module = dag().module()
+ .withDescription("Dagger Java Module example")
+ .withObject(
+ dag().typeDef().withObject("DaggerJava", new TypeDef.WithObjectArguments().withDescription("Dagger Java Module main object"))
+ .withFunction(
+ dag().function("containerEcho",
+ dag().typeDef().withObject("Container"))
+ .withDescription("Returns a container that echoes whatever string argument is provided")
+ .withArg("stringArg", dag().typeDef().withKind(TypeDefKind.STRING_KIND), new Function.WithArgArguments().withDescription("string to echo").withDefaultValue(JSON.from("\"Hello Dagger\""))))
+ .withFunction(
+ dag().function("grepDir",
+ dag().typeDef().withKind(TypeDefKind.STRING_KIND))
+ .withDescription("Returns lines that match a pattern in the files of the provided Directory")
+ .withArg("directoryArg", dag().typeDef().withObject("Directory"), new Function.WithArgArguments().withDescription("Directory to grep").withDefaultPath("sdk/java").withIgnore(List.of("**", "!*.java")))
+ .withArg("pattern", dag().typeDef().withKind(TypeDefKind.STRING_KIND).withOptional(true), new Function.WithArgArguments().withDescription("Pattern to search for in the directory")))
+ .withFunction(
+ dag().function("itself",
+ dag().typeDef().withObject("DaggerJava")))
+ .withFunction(
+ dag().function("isZero",
+ dag().typeDef().withKind(TypeDefKind.BOOLEAN_KIND))
+ .withDescription("but this description should be exposed")
+ .withArg("value", dag().typeDef().withKind(TypeDefKind.INTEGER_KIND)))
+ .withFunction(
+ dag().function("doThings",
+ dag().typeDef().withListOf(dag().typeDef().withKind(TypeDefKind.INTEGER_KIND)))
+ .withArg("stringArray", dag().typeDef().withListOf(dag().typeDef().withKind(TypeDefKind.STRING_KIND)))
+ .withArg("ints", dag().typeDef().withListOf(dag().typeDef().withKind(TypeDefKind.INTEGER_KIND)))
+ .withArg("containers", dag().typeDef().withListOf(dag().typeDef().withObject("Container"))))
+ .withFunction(
+ dag().function("nonNullableNoDefault",
+ dag().typeDef().withKind(TypeDefKind.STRING_KIND))
+ .withDescription("User must provide the argument")
+ .withArg("stringArg", dag().typeDef().withKind(TypeDefKind.STRING_KIND)))
+ .withFunction(
+ dag().function("nonNullableDefault",
+ dag().typeDef().withKind(TypeDefKind.STRING_KIND))
+ .withDescription("If the user doesn't provide an argument, a default value is used. The argument can't be null.")
+ .withArg("stringArg", dag().typeDef().withKind(TypeDefKind.STRING_KIND), new Function.WithArgArguments().withDefaultValue(JSON.from("\"default value\""))))
+ .withFunction(
+ dag().function("nullable",
+ dag().typeDef().withKind(TypeDefKind.STRING_KIND))
+ .withDescription("Make it optional but do not define a value. If the user doesn't provide an argument, it will be\n"
+ + " set to null.")
+ .withArg("stringArg", dag().typeDef().withKind(TypeDefKind.STRING_KIND).withOptional(true)))
+ .withFunction(
+ dag().function("nullableDefault",
+ dag().typeDef().withKind(TypeDefKind.STRING_KIND))
+ .withDescription("Set a default value in case the user doesn't provide a value and allow for null value.")
+ .withArg("stringArg", dag().typeDef().withKind(TypeDefKind.STRING_KIND).withOptional(true), new Function.WithArgArguments().withDefaultValue(JSON.from("\"Foo\""))))
+ .withFunction(
+ dag().function("defaultPlatform",
+ dag().typeDef().withScalar("Platform"))
+ .withDescription("return the default platform as a Scalar value"))
+ .withFunction(
+ dag().function("addFloat",
+ dag().typeDef().withKind(TypeDefKind.FLOAT_KIND))
+ .withArg("a", dag().typeDef().withKind(TypeDefKind.FLOAT_KIND))
+ .withArg("b", dag().typeDef().withKind(TypeDefKind.FLOAT_KIND)))
+ .withFunction(
+ dag().function("doSomething",
+ dag().typeDef().withKind(TypeDefKind.VOID_KIND).withOptional(true))
+ .withDescription("Function returning nothing")
+ .withArg("src", dag().typeDef().withObject("Directory")))
+ .withFunction(
+ dag().function("printSeverity",
+ dag().typeDef().withKind(TypeDefKind.STRING_KIND))
+ .withArg("severity", dag().typeDef().withEnum("Severity")))
+ .withField("source", dag().typeDef().withObject("Directory"), new TypeDef.WithFieldArguments().withDescription("Project source directory"))
+ .withField("version", dag().typeDef().withKind(TypeDefKind.STRING_KIND))
+ .withConstructor(
+ dag().function("",
+ dag().typeDef().withObject("DaggerJava"))
+ .withDescription("Initialize the DaggerJava Module")
+ .withArg("source", dag().typeDef().withObject("Directory").withOptional(true), new Function.WithArgArguments().withDescription("Project source directory"))
+ .withArg("version", dag().typeDef().withKind(TypeDefKind.STRING_KIND), new Function.WithArgArguments().withDescription("Go version").withDefaultValue(JSON.from("\"1.23.2\"")))))
+ .withEnum(
+ dag().typeDef().withEnum("Severity", new TypeDef.WithEnumArguments().withDescription("Severities"))
+ .withEnumValue("DEBUG", new TypeDef.WithEnumValueArguments().withDescription("Debug severity"))
+ .withEnumValue("INFO", new TypeDef.WithEnumValueArguments().withDescription("Info severity"))
+ .withEnumValue("WARN")
+ .withEnumValue("ERROR")
+ .withEnumValue("FATAL"));
+ return module.id();
+ }
+}
\ No newline at end of file
diff --git a/sdk/java/runtime/main.go b/sdk/java/runtime/main.go
index 784943d26be..4940aa82d91 100644
--- a/sdk/java/runtime/main.go
+++ b/sdk/java/runtime/main.go
@@ -35,9 +35,14 @@ type JavaSdk struct {
}
type moduleConfig struct {
- name string
- subPath string
- dirPath string
+ name string
+ subPath string
+ dirPath string
+ pkgName string
+ kebabName string
+ camelName string
+ version string
+ moduleVersion string
}
func (c *moduleConfig) modulePath() string {
@@ -77,7 +82,7 @@ func (m *JavaSdk) Codegen(
modSource *dagger.ModuleSource,
introspectionJSON *dagger.File,
) (*dagger.GeneratedCode, error) {
- if err := m.setModuleConfig(ctx, modSource); err != nil {
+ if err := m.setModuleConfig(ctx, modSource, introspectionJSON); err != nil {
return nil, err
}
mvnCtr, err := m.codegenBase(ctx, modSource, introspectionJSON)
@@ -100,8 +105,8 @@ func (m *JavaSdk) Codegen(
}), nil
}
-// codegenBase takes the user module code, add the generated SDK dependencies
-// if the user module code is empty, creates a default module content based on the template from the SDK
+// codegenBase takes the user module code, add the generated SDK dependencies.
+// If the user module code is empty, creates a default module content based on the template from the SDK
// The generated container will *not* contain the SDK source code, but only the packages built from the SDK
func (m *JavaSdk) codegenBase(
ctx context.Context,
@@ -122,12 +127,6 @@ func (m *JavaSdk) codegenBase(
if err != nil {
return nil, err
}
- // Ensure the version in the pom.xml is the same as the introspection file
- // This is updating the pom.xml whatever it's coming from the template or the user module
- version, err := m.getDaggerVersionForModule(ctx, introspectionJSON)
- if err != nil {
- return nil, err
- }
ctr = ctr.
// set the version of the Dagger dependencies to the version of the introspection file
WithExec(m.mavenCommand(
@@ -135,7 +134,7 @@ func (m *JavaSdk) codegenBase(
"versions:set-property",
"-DgenerateBackupPoms=false",
"-Dproperty=dagger.module.deps",
- fmt.Sprintf("-DnewVersion=%s", version),
+ fmt.Sprintf("-DnewVersion=%s", m.moduleConfig.moduleVersion),
))
return ctr, nil
}
@@ -152,24 +151,41 @@ func (m *JavaSdk) buildJavaDependencies(
if err != nil {
return nil, err
}
- version, err := m.getDaggerVersionForModule(ctx, introspectionJSON)
- if err != nil {
- return nil, err
- }
- return ctr.
+ ctr = ctr.
// Cache maven dependencies
WithMountedCache("/root/.m2", dag.CacheVolume("sdk-java-maven-m2"), dagger.ContainerWithMountedCacheOpts{Sharing: dagger.CacheSharingModeLocked}).
- // Mount the introspection JSON file used to generate the SDK
- WithMountedFile("/schema.json", introspectionJSON).
// Copy the SDK source directory, so all the files needed to build the dependencies
WithDirectory(GenPath, m.SDKSourceDir).
- WithWorkdir(GenPath).
+ WithWorkdir(GenPath)
+ // build the SDK and tools if required
+ for _, project := range []string{
+ "dagger-codegen-maven-plugin",
+ "dagger-java-sdk",
+ "dagger-java-annotation-processor",
+ } {
+ if exitCode, _ := ctr.WithExec(
+ m.mavenCommand(
+ "mvn", "-o", "dependency:get",
+ fmt.Sprintf("-Dartifact=io.dagger:%s:%s", project, m.moduleConfig.version)),
+ dagger.ContainerWithExecOpts{Expect: dagger.ReturnTypeAny}).ExitCode(ctx); exitCode != 0 {
+ _, err = ctr.WithExec(m.mavenCommand(
+ "mvn", "--projects", "dagger-codegen-maven-plugin", "install", "-DskipTests",
+ )).ExitCode(ctx)
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+ return ctr.
+ // Mount the introspection JSON file used to generate the SDK
+ WithMountedFile("/schema.json", introspectionJSON).
+ WithExec([]string{"cat", "/schema.json"}).
// Set the version of the dependencies we are building to the version of the introspection file
WithExec(m.mavenCommand(
"mvn",
"versions:set",
"-DgenerateBackupPoms=false",
- fmt.Sprintf("-DnewVersion=%s", version),
+ fmt.Sprintf("-DnewVersion=%s", m.moduleConfig.moduleVersion),
)).
// Build and install the java modules one by one
// - dagger-codegen-maven-plugin: this plugin will be used to generate the SDK code, from the introspection file,
@@ -196,11 +212,6 @@ func (m *JavaSdk) addTemplate(
ctx context.Context,
ctr *dagger.Container,
) (*dagger.Container, error) {
- name := m.moduleConfig.name
- pkgName := strings.ReplaceAll(strings.ReplaceAll(strings.ToLower(name), "-", ""), "_", "")
- kebabName := strcase.ToKebab(name)
- camelName := strcase.ToCamel(name)
-
// Check if there's a pom.xml inside the module path. If a file exist, no need to add the templates
if _, err := ctr.File(filepath.Join(m.moduleConfig.modulePath(), "pom.xml")).Name(ctx); err == nil {
return ctr, nil
@@ -211,8 +222,9 @@ func (m *JavaSdk) addTemplate(
}
changes := []repl{
- {"dagger-module-placeholder", kebabName},
- {"daggermoduleplaceholder", pkgName},
+ {"dagger-module-placeholder", m.moduleConfig.kebabName},
+ {"dagger-module-typedefs-placeholder", m.moduleConfig.kebabName + "-typedefs"},
+ {"daggermoduleplaceholder", m.moduleConfig.pkgName},
}
// Edit template content so that they match the dagger module name
@@ -223,7 +235,7 @@ func (m *JavaSdk) addTemplate(
return ctr, fmt.Errorf("could not add template: %w", err)
}
- changes = append(changes, repl{"DaggerModule", camelName})
+ changes = append(changes, repl{"DaggerModule", m.moduleConfig.camelName})
daggerModuleJava, err := m.replace(ctx, templateDir,
filepath.Join("src", "main", "java", "io", "dagger", "modules", "daggermodule", "DaggerModule.java"),
changes...)
@@ -240,8 +252,8 @@ func (m *JavaSdk) addTemplate(
// And copy them to the container, renamed to match the dagger module name
ctr = ctr.
WithNewFile(absPath("pom.xml"), pomXML).
- WithNewFile(absPath("src", "main", "java", "io", "dagger", "modules", pkgName, fmt.Sprintf("%s.java", camelName)), daggerModuleJava).
- WithNewFile(absPath("src", "main", "java", "io", "dagger", "modules", pkgName, "package-info.java"), packageInfoJava)
+ WithNewFile(absPath("src", "main", "java", "io", "dagger", "modules", m.moduleConfig.pkgName, fmt.Sprintf("%s.java", m.moduleConfig.camelName)), daggerModuleJava).
+ WithNewFile(absPath("src", "main", "java", "io", "dagger", "modules", m.moduleConfig.pkgName, "package-info.java"), packageInfoJava)
return ctr, nil
}
@@ -292,12 +304,44 @@ func (m *JavaSdk) generateCode(
Directory(ModSourceDirPath), nil
}
+func (m *JavaSdk) ModuleTypeDefs(
+ ctx context.Context,
+ modSource *dagger.ModuleSource,
+ introspectionJSON *dagger.File,
+) (*dagger.Container, error) {
+ if err := m.setModuleConfig(ctx, modSource, introspectionJSON); err != nil {
+ return nil, err
+ }
+
+ // Get a container with the user module sources and the SDK packages built and installed
+ mvnCtr, err := m.codegenBase(ctx, modSource, introspectionJSON)
+ if err != nil {
+ return nil, err
+ }
+ // Build the executable jar
+ jar, err := m.buildTypeDefsJar(ctx, mvnCtr)
+ if err != nil {
+ return nil, err
+ }
+
+ javaCtr, err := m.jreContainer(ctx)
+ if err != nil {
+ return nil, err
+ }
+ javaCtr = javaCtr.
+ WithFile(filepath.Join(ModDirPath, "module.jar"), jar).
+ WithWorkdir(ModDirPath).
+ WithEntrypoint([]string{"java", "-jar", filepath.Join(ModDirPath, "module.jar")})
+
+ return javaCtr, nil
+}
+
func (m *JavaSdk) ModuleRuntime(
ctx context.Context,
modSource *dagger.ModuleSource,
introspectionJSON *dagger.File,
) (*dagger.Container, error) {
- if err := m.setModuleConfig(ctx, modSource); err != nil {
+ if err := m.setModuleConfig(ctx, modSource, introspectionJSON); err != nil {
return nil, err
}
@@ -339,7 +383,59 @@ func (m *JavaSdk) buildJar(
"clean",
"package",
"-DskipTests",
- )))
+ )),
+ m.moduleConfig.modulePath())
+}
+
+// buildTypeDefsJar builds and returns the generated jar to register types
+func (m *JavaSdk) buildTypeDefsJar(
+ ctx context.Context,
+ ctr *dagger.Container,
+) (*dagger.File, error) {
+ out, err := ctr.WithExec([]string{"find", "src/main/java", "-name", "*.java"}).Stdout(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ templateDir := dag.CurrentModule().Source().Directory("template")
+ typeDefsPomXML, err := m.replace(ctx, templateDir,
+ "typedefs/pom.xml",
+ repl{"dagger-module-typedefs-placeholder", m.moduleConfig.kebabName + "-typedefs"},
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ return m.finalJar(ctx,
+ ctr.
+ WithExec(append([]string{
+ "javac",
+ "-cp", fmt.Sprintf("/root/.m2/repository/io/dagger/dagger-java-annotation-processor/%[1]s/dagger-java-annotation-processor-%[1]s-all.jar", m.moduleConfig.moduleVersion),
+ "-processor", "io.dagger.annotation.processor.TypeDefs",
+ "-proc:only",
+ "-d", "target/generated",
+ }, strings.Split(strings.TrimSpace(out), "\n")...)).
+ WithWorkdir(filepath.Join(m.moduleConfig.modulePath(), "typedefs")).
+ WithExec([]string{"mkdir", "-p", "src/main/java/io/dagger/gen/entrypoint"}).
+ WithExec([]string{
+ "cp",
+ "../target/generated/io/dagger/gen/entrypoint/TypeDefs.java",
+ "src/main/java/io/dagger/gen/entrypoint/TypeDefs.java",
+ }).
+ WithNewFile("pom.xml", typeDefsPomXML).
+ WithExec(m.mavenCommand(
+ "mvn",
+ "versions:set-property",
+ "-DgenerateBackupPoms=false",
+ "-Dproperty=dagger.module.deps",
+ fmt.Sprintf("-DnewVersion=%s", m.moduleConfig.moduleVersion),
+ )).
+ WithExec(m.mavenCommand(
+ "mvn",
+ "clean",
+ "package",
+ "-DskipTests")),
+ filepath.Join(m.moduleConfig.modulePath(), "typedefs"))
}
// finalJar will return the jar corresponding to the user module built
@@ -350,6 +446,7 @@ func (m *JavaSdk) buildJar(
func (m *JavaSdk) finalJar(
ctx context.Context,
ctr *dagger.Container,
+ rootDir string,
) (*dagger.File, error) {
artifactID, err := ctr.
WithExec(m.mavenCommand("mvn", "org.apache.maven.plugins:maven-help-plugin:3.2.0:evaluate", "-Dexpression=project.artifactId", "-q", "-DforceStdout")).
@@ -365,7 +462,7 @@ func (m *JavaSdk) finalJar(
}
jarFileName := fmt.Sprintf("%s-%s.jar", artifactID, version)
- return ctr.File(filepath.Join(m.moduleConfig.modulePath(), "target", jarFileName)), nil
+ return ctr.File(filepath.Join(rootDir, "target", jarFileName)), nil
}
func (m *JavaSdk) mvnContainer(ctx context.Context) (*dagger.Container, error) {
@@ -391,11 +488,14 @@ func disableSVEOnArm64(ctx context.Context, ctr *dagger.Container) (*dagger.Cont
return ctr, nil
}
-func (m *JavaSdk) setModuleConfig(ctx context.Context, modSource *dagger.ModuleSource) error {
+func (m *JavaSdk) setModuleConfig(ctx context.Context, modSource *dagger.ModuleSource, introspectionJSON *dagger.File) error {
modName, err := modSource.ModuleName(ctx)
if err != nil {
return err
}
+ pkgName := strings.ReplaceAll(strings.ReplaceAll(strings.ToLower(modName), "-", ""), "_", "")
+ kebabName := strcase.ToKebab(modName)
+ camelName := strcase.ToCamel(modName)
subPath, err := modSource.SourceSubpath(ctx)
if err != nil {
return err
@@ -404,16 +504,26 @@ func (m *JavaSdk) setModuleConfig(ctx context.Context, modSource *dagger.ModuleS
if err != nil {
return err
}
+ version, err := m.getDaggerVersion(ctx, introspectionJSON)
+ if err != nil {
+ return err
+ }
+ moduleVersion := m.getDaggerVersionForModule(version)
m.moduleConfig = moduleConfig{
- name: modName,
- subPath: subPath,
- dirPath: dirPath,
+ name: modName,
+ pkgName: pkgName,
+ kebabName: kebabName,
+ camelName: camelName,
+ subPath: subPath,
+ dirPath: dirPath,
+ version: version,
+ moduleVersion: moduleVersion,
}
return nil
}
-func (m *JavaSdk) getDaggerVersionForModule(ctx context.Context, introspectionJSON *dagger.File) (string, error) {
+func (m *JavaSdk) getDaggerVersion(ctx context.Context, introspectionJSON *dagger.File) (string, error) {
content, err := introspectionJSON.Contents(ctx)
if err != nil {
return "", err
@@ -422,11 +532,15 @@ func (m *JavaSdk) getDaggerVersionForModule(ctx context.Context, introspectionJS
if err = json.Unmarshal([]byte(content), &introspectJSON); err != nil {
return "", err
}
+ return strings.TrimPrefix(introspectJSON.SchemaVersion, "v"), nil
+}
+
+func (m *JavaSdk) getDaggerVersionForModule(version string) string {
return fmt.Sprintf(
"%s-%s-module",
- strings.TrimPrefix(introspectJSON.SchemaVersion, "v"),
+ version,
m.moduleConfig.name,
- ), nil
+ )
}
type IntrospectJSON struct {
diff --git a/sdk/java/runtime/template/typedefs/pom.xml b/sdk/java/runtime/template/typedefs/pom.xml
new file mode 100644
index 00000000000..f2ec5f5bc07
--- /dev/null
+++ b/sdk/java/runtime/template/typedefs/pom.xml
@@ -0,0 +1,116 @@
+
+
+ 4.0.0
+
+ io.dagger.modules.daggermodule
+ dagger-module-typedefs-placeholder
+ 1.0-SNAPSHOT
+ dagger-module-typedefs-placeholder
+
+
+ 17
+ UTF-8
+ 0.18.8-template-module
+
+
+
+
+ io.dagger
+ dagger-java-sdk
+ ${dagger.module.deps}
+
+
+ org.slf4j
+ slf4j-simple
+ runtime
+ 2.0.16
+
+
+ org.eclipse
+ yasson
+ 3.0.4
+ runtime
+
+
+ io.netty
+ netty-handler
+ 4.1.118.Final
+
+
+ net.minidev
+ json-smart
+ 2.5.2
+ runtime
+
+
+
+
+
+
+
+ maven-clean-plugin
+ 3.4.0
+
+
+ maven-resources-plugin
+ 3.3.1
+
+
+ maven-compiler-plugin
+ 3.13.0
+
+
+ maven-surefire-plugin
+ 3.3.0
+
+
+ maven-jar-plugin
+ 3.4.2
+
+
+ maven-install-plugin
+ 3.1.2
+
+
+ maven-deploy-plugin
+ 3.1.2
+
+
+ maven-site-plugin
+ 3.12.1
+
+
+ maven-project-info-reports-plugin
+ 3.6.1
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.5.0
+
+
+ package
+
+ shade
+
+
+
+
+ io.dagger.gen.entrypoint.TypeDefs
+
+
+ ${project.build.outputDirectory}
+ ${project.artifactId}-${project.version}
+
+
+
+
+
+
+
diff --git a/sdk/java/runtime/template/typedefs/src/main/java/io/dagger/gen/entrypoint/.gitkeep b/sdk/java/runtime/template/typedefs/src/main/java/io/dagger/gen/entrypoint/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d