Skip to content

Commit e1b144b

Browse files
xaionaro@dx.centerxaionaro@dx.center
authored andcommitted
feat: spec2go — generate service name constants and Get* accessor files
1 parent 3bda90e commit e1b144b

1 file changed

Lines changed: 177 additions & 0 deletions

File tree

tools/cmd/spec2go/main.go

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -851,6 +851,183 @@ func newRegistryResolver(
851851
return r
852852
}
853853

854+
// generateServiceNamesFile generates servicemanager/service_names_gen.go from
855+
// the Services field of the servicemanager package spec. Each ServiceMapping
856+
// produces a typed constant: ConstantName (SCREAMING_SNAKE) is converted to
857+
// PascalCase, and the value is the service_name string.
858+
func generateServiceNamesFile(
859+
specs map[string]*spec.PackageSpec,
860+
outputDir string,
861+
) error {
862+
smSpec := specs["servicemanager"]
863+
if smSpec == nil || len(smSpec.Services) == 0 {
864+
return nil
865+
}
866+
867+
// Build sorted constant entries.
868+
type constEntry struct {
869+
GoName string
870+
ServiceName string
871+
}
872+
873+
entries := make([]constEntry, 0, len(smSpec.Services))
874+
for _, svc := range smSpec.Services {
875+
goName := codegen.ScreamingSnakeToPascal(svc.ConstantName)
876+
entries = append(entries, constEntry{
877+
GoName: goName,
878+
ServiceName: svc.ServiceName,
879+
})
880+
}
881+
sort.Slice(entries, func(i, j int) bool {
882+
return entries[i].GoName < entries[j].GoName
883+
})
884+
885+
// Find the longest GoName for column alignment.
886+
maxLen := 0
887+
for _, e := range entries {
888+
if len(e.GoName) > maxLen {
889+
maxLen = len(e.GoName)
890+
}
891+
}
892+
893+
var buf bytes.Buffer
894+
buf.WriteString("// Code generated by spec2go. DO NOT EDIT.\n\n")
895+
buf.WriteString("package servicemanager\n\n")
896+
buf.WriteString("// Service name constants extracted from android.content.Context.\n")
897+
buf.WriteString("const (\n")
898+
899+
for _, e := range entries {
900+
padding := strings.Repeat(" ", maxLen-len(e.GoName))
901+
fmt.Fprintf(&buf, "\t%s%s ServiceName = %q\n", e.GoName, padding, e.ServiceName)
902+
}
903+
904+
buf.WriteString(")\n")
905+
906+
formatted, err := format.Source(buf.Bytes())
907+
if err != nil {
908+
return fmt.Errorf("formatting service names: %w\n\nRaw source:\n%s", err, buf.String())
909+
}
910+
911+
outPath := filepath.Join(outputDir, "servicemanager", "service_names_gen.go")
912+
if err := os.MkdirAll(filepath.Dir(outPath), 0o755); err != nil {
913+
return fmt.Errorf("creating directory: %w", err)
914+
}
915+
if err := os.WriteFile(outPath, formatted, 0o644); err != nil {
916+
return fmt.Errorf("writing %s: %w", outPath, err)
917+
}
918+
919+
fmt.Fprintf(os.Stderr, "Wrote %s (%d constants)\n", outPath, len(entries))
920+
return nil
921+
}
922+
923+
// generateAccessorFiles generates get_*.go files for services whose AIDL
924+
// interface has a generated proxy in the output directory. Each file contains
925+
// a GetXxxManager function that calls ServiceManager.GetService and wraps
926+
// the result in the typed proxy constructor.
927+
func generateAccessorFiles(
928+
specs map[string]*spec.PackageSpec,
929+
outputDir string,
930+
) error {
931+
smSpec := specs["servicemanager"]
932+
if smSpec == nil || len(smSpec.Services) == 0 {
933+
return nil
934+
}
935+
936+
// Build a descriptor -> InterfaceSpec index across all specs so we can
937+
// look up the interface definition for each service's descriptor.
938+
type ifaceInfo struct {
939+
GoPackage string
940+
InterfaceName string
941+
}
942+
descriptorIndex := map[string]ifaceInfo{}
943+
for _, ps := range specs {
944+
for _, iface := range ps.Interfaces {
945+
desc := ps.AIDLPackage + "." + iface.Name
946+
descriptorIndex[desc] = ifaceInfo{
947+
GoPackage: ps.GoPackage,
948+
InterfaceName: iface.Name,
949+
}
950+
}
951+
}
952+
953+
count := 0
954+
for _, svc := range smSpec.Services {
955+
info, ok := descriptorIndex[svc.Descriptor]
956+
if !ok {
957+
continue
958+
}
959+
960+
interfaceGoName := codegen.AIDLToGoName(info.InterfaceName)
961+
proxyName := deriveProxyName(interfaceGoName)
962+
constructorName := "New" + proxyName
963+
964+
// Verify the proxy constructor exists in the output directory by
965+
// checking that the generated interface file is present.
966+
goFileName := codegen.AIDLToGoFileName(info.InterfaceName)
967+
ifaceFilePath := filepath.Join(outputDir, info.GoPackage, goFileName)
968+
if _, err := os.Stat(ifaceFilePath); err != nil {
969+
continue
970+
}
971+
972+
// Determine the Go package name (last segment of the package path).
973+
goPkg := filepath.Base(info.GoPackage)
974+
975+
// Build the constant name for the servicemanager constant.
976+
constName := codegen.ScreamingSnakeToPascal(svc.ConstantName)
977+
978+
// The base name without Proxy suffix is used for the Get function.
979+
baseName := interfaceGoName
980+
if strings.HasPrefix(interfaceGoName, "I") && len(interfaceGoName) > 1 {
981+
baseName = interfaceGoName[1:]
982+
}
983+
funcName := "Get" + baseName
984+
985+
var buf bytes.Buffer
986+
buf.WriteString("// Code generated by spec2go. DO NOT EDIT.\n\n")
987+
fmt.Fprintf(&buf, "package %s\n\n", goPkg)
988+
buf.WriteString("import (\n")
989+
buf.WriteString("\t\"context\"\n")
990+
buf.WriteString("\t\"fmt\"\n\n")
991+
buf.WriteString("\t\"github.com/xaionaro-go/binder/servicemanager\"\n")
992+
buf.WriteString(")\n\n")
993+
fmt.Fprintf(&buf, "// %s retrieves the %s service and returns a typed proxy.\n", funcName, constName)
994+
fmt.Fprintf(&buf, "func %s(\n", funcName)
995+
buf.WriteString("\tctx context.Context,\n")
996+
buf.WriteString("\tsm *servicemanager.ServiceManager,\n")
997+
fmt.Fprintf(&buf, ") (*%s, error) {\n", proxyName)
998+
fmt.Fprintf(&buf, "\tsvc, err := sm.GetService(ctx, servicemanager.%s)\n", constName)
999+
buf.WriteString("\tif err != nil {\n")
1000+
fmt.Fprintf(&buf, "\t\treturn nil, fmt.Errorf(\"%s: %%w\", err)\n", funcName)
1001+
buf.WriteString("\t}\n")
1002+
fmt.Fprintf(&buf, "\treturn %s(svc), nil\n", constructorName)
1003+
buf.WriteString("}\n")
1004+
1005+
formatted, fmtErr := format.Source(buf.Bytes())
1006+
if fmtErr != nil {
1007+
return fmt.Errorf("formatting accessor for %s: %w\n\nRaw source:\n%s", svc.ServiceName, fmtErr, buf.String())
1008+
}
1009+
1010+
outPath := filepath.Join(outputDir, info.GoPackage, "get_"+svc.ServiceName+".go")
1011+
if err := os.WriteFile(outPath, formatted, 0o644); err != nil {
1012+
return fmt.Errorf("writing %s: %w", outPath, err)
1013+
}
1014+
count++
1015+
}
1016+
1017+
fmt.Fprintf(os.Stderr, "Generated %d accessor files\n", count)
1018+
return nil
1019+
}
1020+
1021+
// deriveProxyName derives a proxy struct name from an interface name.
1022+
// IFoo -> FooProxy, Foo -> FooProxy. This mirrors the codegen convention.
1023+
func deriveProxyName(interfaceName string) string {
1024+
base := interfaceName
1025+
if strings.HasPrefix(interfaceName, "I") && len(interfaceName) > 1 {
1026+
base = interfaceName[1:]
1027+
}
1028+
return base + "Proxy"
1029+
}
1030+
8541031
// generateCodesFile builds codes_gen.go from version_codes embedded in
8551032
// interface specs.
8561033
func generateCodesFile(

0 commit comments

Comments
 (0)