@@ -73,7 +73,10 @@ CommandGroup[] getCommands() @safe pure nothrow
7373 new ListOverridesCommand,
7474 new CleanCachesCommand,
7575 new ConvertCommand,
76- )
76+ // This is index management but those commands are hidden
77+ new IndexBuildCommand,
78+ new IndexFromRegistryCommand,
79+ ),
7780 ];
7881}
7982
@@ -2983,6 +2986,255 @@ class ConvertCommand : Command {
29832986}
29842987
29852988
2989+ /* *****************************************************************************/
2990+ /* Index management
2991+ /******************************************************************************/
2992+
2993+ public class IndexBuildCommand : Command {
2994+ import dub.index.bitbucket;
2995+ import dub.index.data;
2996+ import dub.index.github;
2997+ import dub.index.gitlab;
2998+ import dub.index.utils;
2999+ import std.random ;
3000+ import std.range ;
3001+
3002+ // / Index file to use
3003+ private string index = " index.yaml" ;
3004+ // / Filename to write to
3005+ private string output = " index-build-result" ;
3006+ // / Packages to filter in - assume all if empty
3007+ private string [] include;
3008+ // / Packages to filter out
3009+ private string [] exclude;
3010+ // / Bearer token to use to authenticate requests
3011+ private string githubToken, gitlabToken, bitbucketToken;
3012+ // / Kind of packages to include (default: all kinds)
3013+ private string [] kind;
3014+ // / Whether to force the iteration of tags or not
3015+ // / This needs to be used if some tags need to be reprocessed
3016+ private bool force_tags;
3017+ // / Force the package to be entirely reprocessed. Imply `--force-tags`.
3018+ private bool force;
3019+ // / Whether to use a randomized sample of packages
3020+ private bool random;
3021+ // / The number of packages to update
3022+ private uint maxUpdates = uint .max;
3023+ // / Source and target index to use, mutually exclusive with `random`
3024+ private uint fromIdx = 0 , toIdx = uint .max;
3025+
3026+ this () @safe pure nothrow
3027+ {
3028+ this .name = " index-build" ;
3029+ this .description = " Generate the rich index from the index.yaml file" ;
3030+ this .helpText = [ " This command is for internal use only. Do not use it." ];
3031+ this .hidden = true ;
3032+ }
3033+
3034+ override void prepare (scope CommandArgs args) {
3035+ args.getopt(" bitbucket-token" , &this .bitbucketToken, [" Bearer token to use when issuing Bitbucket requests" ]);
3036+ args.getopt(" github-token" , &this .githubToken, [" Bearer token to use when issuing Github requests" ]);
3037+ args.getopt(" gitlab-token" , &this .gitlabToken, [" Bearer token to use when issuing GitLab requests" ]);
3038+ args.getopt(" output" , &this .output, [" Where to output the data (path to a folder)" ]);
3039+ args.getopt(" index" , &this .index, [" Index file to use - default to 'index.yaml'" ]);
3040+ args.getopt(" include" , &this .include, [" Which packages to filter in - if not, assume all" ]);
3041+ args.getopt(" exclude" , &this .exclude, [" Which packages to filter out - if not, assume none" ]);
3042+ args.getopt(" kind" , &this .kind, [" Kind of packages to include (github, gitlab, bitbucket). Default: all" ]);
3043+ args.getopt(" force" , &this .force, [" Force Dub to reprocess packages even if it has cache informations" ]);
3044+ args.getopt(" force-tags" , &this .force_tags,
3045+ [" Force Dub to re-list tags, but do not reload the recipe if the commit hasn't changed." ]);
3046+ args.getopt(" random" , &this .random, [" Randomize the order in which packages are processed" ]);
3047+ args.getopt(" max-updates" , &this .maxUpdates, [" Maximum number of packages to process" ]);
3048+ args.getopt(" from" , &this .fromIdx, [" Index to seek to before iterating the list of packages (default: 0)" ]);
3049+ args.getopt(" to" , &this .toIdx, [" Index to stop at when iterating the list of packages (default: end of list)" ]);
3050+
3051+ enforce(this .fromIdx <= this .toIdx, " Cannot have source index (`--from`) be past end index (`--to`)" );
3052+ enforce(this .fromIdx == 0 || ! this .random,
3053+ " Cannot specify source index (`--from`) for random sampling (`--random`)" );
3054+ enforce(this .toIdx == uint .max || ! this .random,
3055+ " Cannot specify end index (`--to`) for random sampling (`--random`)" );
3056+ }
3057+
3058+ override int execute (Dub dub, string [] free_args, string [] app_args)
3059+ {
3060+ import dub.index.client : RepositoryClient;
3061+ import dub.index.data;
3062+ import dub.internal.configy.easy;
3063+ static import std.file ;
3064+ import std.typecons ;
3065+
3066+ enforceUsage(free_args.length == 0 , " Expected no free argument." );
3067+ enforceUsage(app_args.length == 0 , " Expected zero application arguments." );
3068+
3069+ const isUpdate = std.file.exists (this .output);
3070+ if (isUpdate)
3071+ enforce(std.file.isDir (this .output), this .output ~ " : is not a directory" );
3072+ else
3073+ std.file.mkdirRecurse (this .output);
3074+
3075+ auto indexN = parseConfigFileSimple! PackageList(this .index);
3076+ if (indexN.isNull()) return 1 ;
3077+ auto indexC = indexN.get ();
3078+ logInfoNoTag(" Found %s packages in the index file" , indexC.packages.length);
3079+
3080+ const NativePath outputPath = NativePath(this .output);
3081+ size_t processed, updated, notsupported;
3082+ string [] included, excluded, errored;
3083+ scope gh = new GithubClient(this .githubToken);
3084+ scope gl = new GitLabClient(this .gitlabToken);
3085+ scope bb = new BitbucketClient(this .bitbucketToken);
3086+
3087+ void update (scope RepositoryClient client, in PackageEntry pkg) {
3088+ const target = getPackageDescriptionPath(outputPath, PackageName(pkg.name.value));
3089+ ensureDirectory(target.parentPath());
3090+ const targetStr = target.toNativeString();
3091+ auto previous = ! this .force && std.file.exists (targetStr) ?
3092+ parseConfigFileSimple! (IndexedPackage! 0 )(targetStr, StrictMode.Ignore) :
3093+ Nullable! (IndexedPackage! 0 ).init;
3094+ if (this .force_tags && ! previous.isNull())
3095+ previous.get ().cache = CacheInfo.init;
3096+ auto res = updateDescription(client, pkg, previous);
3097+ if (previous.isNull() || previous.get () != res) {
3098+ std.file.write (targetStr, res.serializeToJsonString());
3099+ ++ updated;
3100+ }
3101+ }
3102+
3103+ // Update a single package - this code is in its own function as it
3104+ // is wrapped in a try-catch in the `foreach` to process as many packages
3105+ // as possible
3106+ void updatePackageIndex (in PackageEntry pkg) {
3107+ logInfo(" [%s] Processing included package" , pkg.name.value);
3108+ switch (pkg.source.kind) {
3109+ case ` github` :
3110+ scope client = gh.new Repository(pkg.source.owner, pkg.source.project);
3111+ update(client, pkg);
3112+ break ;
3113+ case ` gitlab` :
3114+ scope client = gl.new Project(pkg.source.owner, pkg.source.project);
3115+ update(client, pkg);
3116+ break ;
3117+ case ` bitbucket` :
3118+ scope client = bb.new Repository(pkg.source.owner, pkg.source.project);
3119+ update(client, pkg);
3120+ break ;
3121+ default :
3122+ throw new Exception (" Package kind not supported: " ~ pkg.source.kind);
3123+ }
3124+ }
3125+
3126+ int processEntry (size_t idx, ref PackageEntry pkg) {
3127+ if (updated >= this .maxUpdates) return 1 ;
3128+ if (this .include.length && ! this .include.canFind(pkg.name.value))
3129+ return 0 ;
3130+ if (this .exclude.canFind(pkg.name.value)) {
3131+ excluded ~= pkg.name.value;
3132+ return 0 ;
3133+ }
3134+ if (this .kind.length && ! this .kind.canFind(pkg.source.kind))
3135+ return 0 ;
3136+
3137+ ++ processed;
3138+ try
3139+ updatePackageIndex(pkg);
3140+ catch (Exception exc) {
3141+ errored ~= pkg.name.value;
3142+ // If we get a 404 here, it might be a dead package
3143+ logError(" [%s] Could not build index for package: %s" ,
3144+ pkg.name.value, exc.message());
3145+ }
3146+
3147+ if (this .include.length) {
3148+ included ~= pkg.name.value;
3149+ if (included.length % 10 == 0 ) {
3150+ const rl = gh.getRateLimit();
3151+ logDebug(" Requests still available: %s/%s" , rl.remaining, rl.limit);
3152+ }
3153+ }
3154+ else if (idx % 10 == 0 ) {
3155+ const rl = gh.getRateLimit();
3156+ logDebug(" Requests still available: %s/%s" , rl.remaining, rl.limit);
3157+ }
3158+ return 0 ;
3159+ }
3160+
3161+ if (this .random) { // Can't use `std.random : choose` because bugs
3162+ foreach (idx, pkg; indexC.packages.randomCover().enumerate)
3163+ if (processEntry(idx, pkg))
3164+ break ;
3165+ } else {
3166+ const startIdx = min(this .fromIdx, indexC.packages.length);
3167+ const endIdx = min(this .toIdx, indexC.packages.length);
3168+ foreach (idx, pkg; indexC.packages[startIdx .. endIdx])
3169+ if (processEntry(idx, pkg))
3170+ break ;
3171+ }
3172+
3173+ logInfoNoTag(" Updated %s packages out of %s processed (%s excluded, %s errors, %s not supported)" ,
3174+ updated, processed, excluded.length, errored.length, notsupported);
3175+ if (this .include.length && included != this .include)
3176+ logWarn(" Not all explicitly-included packages have been processed!" );
3177+ if (errored.length)
3178+ logWarn(" The following packages errored out:\n %(\t - %s\n %)" , errored);
3179+ if (! this .kind.length || this .kind.canFind(` github` )) {
3180+ const rl = gh.getRateLimit();
3181+ logInfoNoTag(" Github requests still available: %s/%s" , rl.remaining, rl.limit);
3182+ }
3183+ return 0 ;
3184+ }
3185+ }
3186+
3187+ public class IndexFromRegistryCommand : Command {
3188+ // / Filename to write to
3189+ private string output = " index.yaml" ;
3190+ // / Bypass cache, always query the registry
3191+ private bool force;
3192+
3193+ this () @safe pure nothrow
3194+ {
3195+ this .name = " index-fromregistry" ;
3196+ this .description = " Generate the index.yaml file from the remote registry" ;
3197+ this .helpText = [ " This command is for internal use only. Do not use it." ];
3198+ this .hidden = true ;
3199+ }
3200+
3201+ override void prepare (scope CommandArgs args) {
3202+ args.getopt(" O" , &this .output, [" Where to output the data ('-' is supported)" ]);
3203+ args.getopt(" f" , &this .force, [" Bypass the cache and always query the registry" ]);
3204+ }
3205+
3206+ override int execute (Dub dub, string [] free_args, string [] app_args)
3207+ {
3208+ import dub.internal.vibecompat.inet.url;
3209+ import dub.packagesuppliers.registry;
3210+ import std.format ;
3211+
3212+ enforceUsage(free_args.length == 0 , " Expected zero arguments." );
3213+ enforceUsage(app_args.length == 0 , " Expected zero application arguments." );
3214+
3215+ scope registry = new RegistryPackageSupplier(URL (defaultRegistryURLs[1 ]));
3216+ scope allPkgs = registry.getPackageDump(this .force);
3217+ writeln(" Found " , allPkgs.array.length, " packages" );
3218+ scope output = this .output == " -" ? stdout : File (this .output, " w+" );
3219+ scope writer = output.lockingTextWriter();
3220+ writer.formattedWrite(" packages:\n " );
3221+ foreach (pkg; allPkgs.array) {
3222+ writer.formattedWrite(` %s:
3223+ source:
3224+ kind: %s
3225+ owner: %s
3226+ project: %s
3227+ ` ,
3228+ pkg[" name" ].opt! string , pkg[" repository" ][" kind" ].opt! string ,
3229+ pkg[" repository" ][" owner" ].opt! string ,
3230+ pkg[" repository" ][" project" ].opt! string );
3231+ }
3232+
3233+ return 0 ;
3234+ }
3235+ }
3236+
3237+
29863238/* *****************************************************************************/
29873239/* HELP */
29883240/* *****************************************************************************/
0 commit comments